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 generate_select(&mut self, select: &Select) -> Result<()> {
4068        use crate::dialects::DialectType;
4069
4070        // Redshift-style EXCLUDE: for dialects other than Redshift, wrap in a derived table
4071        // e.g., SELECT *, col4 EXCLUDE (col2, col3) FROM t
4072        //   → SELECT * EXCLUDE (col2, col3) FROM (SELECT *, col4 FROM t)
4073        if let Some(exclude) = &select.exclude {
4074            if !exclude.is_empty() && !matches!(self.config.dialect, Some(DialectType::Redshift)) {
4075                // Build the inner select (same as original but without exclude)
4076                let mut inner_select = select.clone();
4077                inner_select.exclude = None;
4078                let inner_expr = Expression::Select(Box::new(inner_select));
4079
4080                // Build the subquery
4081                let subquery = crate::expressions::Subquery {
4082                    this: inner_expr,
4083                    alias: None,
4084                    column_aliases: Vec::new(),
4085                    alias_explicit_as: false,
4086                    alias_keyword: None,
4087                    order_by: None,
4088                    limit: None,
4089                    offset: None,
4090                    distribute_by: None,
4091                    sort_by: None,
4092                    cluster_by: None,
4093                    lateral: false,
4094                    modifiers_inside: false,
4095                    trailing_comments: Vec::new(),
4096                    inferred_type: None,
4097                };
4098
4099                // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner)
4100                let star = Expression::Star(crate::expressions::Star {
4101                    table: None,
4102                    except: Some(
4103                        exclude
4104                            .iter()
4105                            .map(|e| match e {
4106                                Expression::Column(col) => col.name.clone(),
4107                                Expression::Identifier(id) => id.clone(),
4108                                _ => crate::expressions::Identifier::new("unknown".to_string()),
4109                            })
4110                            .collect(),
4111                    ),
4112                    replace: None,
4113                    rename: None,
4114                    trailing_comments: Vec::new(),
4115                    span: None,
4116                });
4117
4118                let outer_select = Select {
4119                    expressions: vec![star],
4120                    from: Some(crate::expressions::From {
4121                        expressions: vec![Expression::Subquery(Box::new(subquery))],
4122                    }),
4123                    ..Select::new()
4124                };
4125
4126                return self.generate_select(&outer_select);
4127            }
4128        }
4129
4130        // Output leading comments before SELECT
4131        for comment in &select.leading_comments {
4132            self.write_formatted_comment(comment);
4133            self.write(" ");
4134        }
4135
4136        // WITH clause
4137        if let Some(with) = &select.with {
4138            self.generate_with(with)?;
4139            if self.config.pretty {
4140                self.write_newline();
4141                self.write_indent();
4142            } else {
4143                self.write_space();
4144            }
4145        }
4146
4147        // Output post-SELECT comments (comments that appeared after SELECT keyword)
4148        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
4149        for comment in &select.post_select_comments {
4150            self.write_formatted_comment(comment);
4151            self.write(" ");
4152        }
4153
4154        self.write_keyword("SELECT");
4155
4156        // Generate query hint if present /*+ ... */
4157        if let Some(hint) = &select.hint {
4158            self.generate_hint(hint)?;
4159        }
4160
4161        // For SQL Server, convert LIMIT to TOP (structural transformation)
4162        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
4163        // TOP clause (SQL Server style - before DISTINCT)
4164        let use_top_from_limit = matches!(
4165            self.config.dialect,
4166            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4167        ) && select.top.is_none()
4168            && select.limit.is_some()
4169            && select.offset.is_none(); // Don't use TOP when there's OFFSET
4170
4171        // For TOP-supporting dialects: DISTINCT before TOP
4172        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
4173        let is_top_dialect = matches!(
4174            self.config.dialect,
4175            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
4176        );
4177        let keep_top_verbatim = !is_top_dialect
4178            && select.limit.is_none()
4179            && select
4180                .top
4181                .as_ref()
4182                .map_or(false, |top| top.percent || top.with_ties);
4183
4184        if select.distinct && (is_top_dialect || select.top.is_some()) {
4185            self.write_space();
4186            self.write_keyword("DISTINCT");
4187        }
4188
4189        if is_top_dialect || keep_top_verbatim {
4190            if let Some(top) = &select.top {
4191                self.write_space();
4192                self.write_keyword("TOP");
4193                if top.parenthesized {
4194                    if matches!(&top.this, Expression::Subquery(_) | Expression::Paren(_)) {
4195                        self.write_space();
4196                        self.generate_expression(&top.this)?;
4197                    } else {
4198                        self.write(" (");
4199                        self.generate_expression(&top.this)?;
4200                        self.write(")");
4201                    }
4202                } else {
4203                    self.write_space();
4204                    self.generate_expression(&top.this)?;
4205                }
4206                if top.percent {
4207                    self.write_space();
4208                    self.write_keyword("PERCENT");
4209                }
4210                if top.with_ties {
4211                    self.write_space();
4212                    self.write_keyword("WITH TIES");
4213                }
4214            } else if use_top_from_limit {
4215                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4216                if let Some(limit) = &select.limit {
4217                    self.write_space();
4218                    self.write_keyword("TOP");
4219                    // Use parentheses for complex expressions, but not for simple literals
4220                    let is_simple_literal = matches!(&limit.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4221                    if is_simple_literal {
4222                        self.write_space();
4223                        self.generate_expression(&limit.this)?;
4224                    } else {
4225                        self.write(" (");
4226                        self.generate_expression(&limit.this)?;
4227                        self.write(")");
4228                    }
4229                }
4230            }
4231        }
4232
4233        if select.distinct && !is_top_dialect && select.top.is_none() {
4234            self.write_space();
4235            self.write_keyword("DISTINCT");
4236        }
4237
4238        // DISTINCT ON clause (PostgreSQL)
4239        if let Some(distinct_on) = &select.distinct_on {
4240            self.write_space();
4241            self.write_keyword("ON");
4242            self.write(" (");
4243            for (i, expr) in distinct_on.iter().enumerate() {
4244                if i > 0 {
4245                    self.write(", ");
4246                }
4247                self.generate_expression(expr)?;
4248            }
4249            self.write(")");
4250        }
4251
4252        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4253        for modifier in &select.operation_modifiers {
4254            self.write_space();
4255            self.write_keyword(modifier);
4256        }
4257
4258        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4259        if let Some(kind) = &select.kind {
4260            self.write_space();
4261            self.write_keyword("AS");
4262            self.write_space();
4263            self.write_keyword(kind);
4264        }
4265
4266        // Expressions (only if there are any)
4267        if !select.expressions.is_empty() {
4268            if self.config.pretty {
4269                self.write_newline();
4270                self.indent_level += 1;
4271            } else {
4272                self.write_space();
4273            }
4274        }
4275
4276        for (i, expr) in select.expressions.iter().enumerate() {
4277            if i > 0 {
4278                self.write(",");
4279                if self.config.pretty {
4280                    self.write_newline();
4281                } else {
4282                    self.write_space();
4283                }
4284            }
4285            if self.config.pretty {
4286                self.write_indent();
4287            }
4288            self.generate_expression(expr)?;
4289        }
4290
4291        if self.config.pretty && !select.expressions.is_empty() {
4292            self.indent_level -= 1;
4293        }
4294
4295        // Redshift-style EXCLUDE clause at the end of the projection list
4296        // For Redshift dialect: append EXCLUDE (col1, col2) after the expressions
4297        // For other dialects (DuckDB, Snowflake): this is handled by wrapping in a derived table
4298        // (done after the full select is generated below)
4299        if let Some(exclude) = &select.exclude {
4300            if !exclude.is_empty() && matches!(self.config.dialect, Some(DialectType::Redshift)) {
4301                self.write_space();
4302                self.write_keyword("EXCLUDE");
4303                self.write(" (");
4304                for (i, col) in exclude.iter().enumerate() {
4305                    if i > 0 {
4306                        self.write(", ");
4307                    }
4308                    self.generate_expression(col)?;
4309                }
4310                self.write(")");
4311            }
4312        }
4313
4314        // INTO clause (SELECT ... INTO table_name)
4315        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4316        if let Some(into) = &select.into {
4317            if self.config.pretty {
4318                self.write_newline();
4319                self.write_indent();
4320            } else {
4321                self.write_space();
4322            }
4323            if into.bulk_collect {
4324                self.write_keyword("BULK COLLECT INTO");
4325            } else {
4326                self.write_keyword("INTO");
4327            }
4328            if into.temporary {
4329                self.write_space();
4330                self.write_keyword("TEMPORARY");
4331            }
4332            if into.unlogged {
4333                self.write_space();
4334                self.write_keyword("UNLOGGED");
4335            }
4336            self.write_space();
4337            // If we have multiple expressions, output them comma-separated
4338            if !into.expressions.is_empty() {
4339                for (i, expr) in into.expressions.iter().enumerate() {
4340                    if i > 0 {
4341                        self.write(", ");
4342                    }
4343                    self.generate_expression(expr)?;
4344                }
4345            } else {
4346                self.generate_expression(&into.this)?;
4347            }
4348        }
4349
4350        // FROM clause
4351        if let Some(from) = &select.from {
4352            if self.config.pretty {
4353                self.write_newline();
4354                self.write_indent();
4355            } else {
4356                self.write_space();
4357            }
4358            self.write_keyword("FROM");
4359            self.write_space();
4360
4361            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4362            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4363            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4364            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4365            let has_tablesample = from
4366                .expressions
4367                .iter()
4368                .any(|e| matches!(e, Expression::TableSample(_)));
4369            let is_cross_join_dialect = matches!(
4370                self.config.dialect,
4371                Some(DialectType::BigQuery)
4372                    | Some(DialectType::Hive)
4373                    | Some(DialectType::Spark)
4374                    | Some(DialectType::Databricks)
4375                    | Some(DialectType::SQLite)
4376                    | Some(DialectType::ClickHouse)
4377            );
4378            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4379            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4380            let source_is_same_as_target = self.config.source_dialect.is_some()
4381                && self.config.source_dialect == self.config.dialect;
4382            let source_is_cross_join_dialect = matches!(
4383                self.config.source_dialect,
4384                Some(DialectType::BigQuery)
4385                    | Some(DialectType::Hive)
4386                    | Some(DialectType::Spark)
4387                    | Some(DialectType::Databricks)
4388                    | Some(DialectType::SQLite)
4389                    | Some(DialectType::ClickHouse)
4390            );
4391            let use_cross_join = !has_tablesample
4392                && is_cross_join_dialect
4393                && (source_is_same_as_target
4394                    || source_is_cross_join_dialect
4395                    || self.config.source_dialect.is_none());
4396
4397            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4398            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4399
4400            for (i, expr) in from.expressions.iter().enumerate() {
4401                if i > 0 {
4402                    if use_cross_join {
4403                        self.write(" CROSS JOIN ");
4404                    } else {
4405                        self.write(", ");
4406                    }
4407                }
4408                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4409                    self.write("(");
4410                    self.generate_expression(expr)?;
4411                    self.write(")");
4412                } else {
4413                    self.generate_expression(expr)?;
4414                }
4415                // Output leading comments that were on the table name before FROM
4416                // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
4417                let leading = Self::extract_table_leading_comments(expr);
4418                for comment in &leading {
4419                    self.write_space();
4420                    self.write_formatted_comment(comment);
4421                }
4422            }
4423        }
4424
4425        // JOINs - handle nested join structure for pretty printing
4426        // Deferred-condition joins "own" the non-deferred joins that follow them
4427        // until the next deferred join or end of list
4428        if self.config.pretty {
4429            self.generate_joins_with_nesting(&select.joins)?;
4430        } else {
4431            for join in &select.joins {
4432                self.generate_join(join)?;
4433            }
4434            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4435            for join in select.joins.iter().rev() {
4436                if join.deferred_condition {
4437                    self.generate_join_condition(join)?;
4438                }
4439            }
4440        }
4441
4442        // LATERAL VIEW clauses (Hive/Spark)
4443        for (lv_idx, lateral_view) in select.lateral_views.iter().enumerate() {
4444            self.generate_lateral_view(lateral_view, lv_idx)?;
4445        }
4446
4447        // PREWHERE (ClickHouse)
4448        if let Some(prewhere) = &select.prewhere {
4449            self.write_clause_condition("PREWHERE", prewhere)?;
4450        }
4451
4452        // WHERE
4453        if let Some(where_clause) = &select.where_clause {
4454            self.write_clause_condition("WHERE", &where_clause.this)?;
4455        }
4456
4457        // CONNECT BY (Oracle hierarchical queries)
4458        if let Some(connect) = &select.connect {
4459            self.generate_connect(connect)?;
4460        }
4461
4462        // GROUP BY
4463        if let Some(group_by) = &select.group_by {
4464            if self.config.pretty {
4465                // Output leading comments on their own lines before GROUP BY
4466                for comment in &group_by.comments {
4467                    self.write_newline();
4468                    self.write_indent();
4469                    self.write_formatted_comment(comment);
4470                }
4471                self.write_newline();
4472                self.write_indent();
4473            } else {
4474                self.write_space();
4475                // In non-pretty mode, output comments inline
4476                for comment in &group_by.comments {
4477                    self.write_formatted_comment(comment);
4478                    self.write_space();
4479                }
4480            }
4481            let clickhouse_bare_modifiers =
4482                matches!(self.config.dialect, Some(DialectType::ClickHouse))
4483                    && group_by.all.is_none()
4484                    && (group_by.totals || !group_by.expressions.is_empty())
4485                    && group_by.expressions.iter().all(|expr| match expr {
4486                        Expression::Cube(c) => c.expressions.is_empty(),
4487                        Expression::Rollup(r) => r.expressions.is_empty(),
4488                        _ => false,
4489                    });
4490
4491            if clickhouse_bare_modifiers {
4492                let trailing_cube = group_by
4493                    .expressions
4494                    .iter()
4495                    .any(|expr| matches!(expr, Expression::Cube(c) if c.expressions.is_empty()));
4496                let trailing_rollup = group_by
4497                    .expressions
4498                    .iter()
4499                    .any(|expr| matches!(expr, Expression::Rollup(r) if r.expressions.is_empty()));
4500
4501                if trailing_cube {
4502                    self.write_keyword("WITH CUBE");
4503                } else if trailing_rollup {
4504                    self.write_keyword("WITH ROLLUP");
4505                }
4506
4507                if group_by.totals {
4508                    if trailing_cube || trailing_rollup {
4509                        self.write_space();
4510                    }
4511                    self.write_keyword("WITH TOTALS");
4512                }
4513            } else {
4514                self.write_keyword("GROUP BY");
4515                // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4516                match group_by.all {
4517                    Some(true) => {
4518                        self.write_space();
4519                        self.write_keyword("ALL");
4520                    }
4521                    Some(false) => {
4522                        self.write_space();
4523                        self.write_keyword("DISTINCT");
4524                    }
4525                    None => {}
4526                }
4527                if !group_by.expressions.is_empty() {
4528                    // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4529                    // These are represented as Cube/Rollup expressions with empty expressions at the end
4530                    let mut trailing_cube = false;
4531                    let mut trailing_rollup = false;
4532                    let mut plain_expressions: Vec<&Expression> = Vec::new();
4533                    let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4534                    let mut cube_expressions: Vec<&Expression> = Vec::new();
4535                    let mut rollup_expressions: Vec<&Expression> = Vec::new();
4536
4537                    for expr in &group_by.expressions {
4538                        match expr {
4539                            Expression::Cube(c) if c.expressions.is_empty() => {
4540                                trailing_cube = true;
4541                            }
4542                            Expression::Rollup(r) if r.expressions.is_empty() => {
4543                                trailing_rollup = true;
4544                            }
4545                            Expression::Function(f) if f.name == "CUBE" => {
4546                                cube_expressions.push(expr);
4547                            }
4548                            Expression::Function(f) if f.name == "ROLLUP" => {
4549                                rollup_expressions.push(expr);
4550                            }
4551                            Expression::Function(f) if f.name == "GROUPING SETS" => {
4552                                grouping_sets_expressions.push(expr);
4553                            }
4554                            _ => {
4555                                plain_expressions.push(expr);
4556                            }
4557                        }
4558                    }
4559
4560                    // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4561                    let mut regular_expressions: Vec<&Expression> = Vec::new();
4562                    regular_expressions.extend(plain_expressions);
4563                    regular_expressions.extend(grouping_sets_expressions);
4564                    regular_expressions.extend(cube_expressions);
4565                    regular_expressions.extend(rollup_expressions);
4566
4567                    if self.config.pretty {
4568                        self.write_newline();
4569                        self.indent_level += 1;
4570                        self.write_indent();
4571                    } else {
4572                        self.write_space();
4573                    }
4574
4575                    for (i, expr) in regular_expressions.iter().enumerate() {
4576                        if i > 0 {
4577                            if self.config.pretty {
4578                                self.write(",");
4579                                self.write_newline();
4580                                self.write_indent();
4581                            } else {
4582                                self.write(", ");
4583                            }
4584                        }
4585                        self.generate_expression(expr)?;
4586                    }
4587
4588                    if self.config.pretty {
4589                        self.indent_level -= 1;
4590                    }
4591
4592                    // Output trailing WITH CUBE or WITH ROLLUP
4593                    if trailing_cube {
4594                        self.write_space();
4595                        self.write_keyword("WITH CUBE");
4596                    } else if trailing_rollup {
4597                        self.write_space();
4598                        self.write_keyword("WITH ROLLUP");
4599                    }
4600                }
4601
4602                // ClickHouse: WITH TOTALS
4603                if group_by.totals {
4604                    self.write_space();
4605                    self.write_keyword("WITH TOTALS");
4606                }
4607            }
4608        }
4609
4610        // HAVING
4611        if let Some(having) = &select.having {
4612            if self.config.pretty {
4613                // Output leading comments on their own lines before HAVING
4614                for comment in &having.comments {
4615                    self.write_newline();
4616                    self.write_indent();
4617                    self.write_formatted_comment(comment);
4618                }
4619            } else {
4620                for comment in &having.comments {
4621                    self.write_space();
4622                    self.write_formatted_comment(comment);
4623                }
4624            }
4625            self.write_clause_condition("HAVING", &having.this)?;
4626        }
4627
4628        // QUALIFY and WINDOW clause ordering depends on input SQL
4629        if select.qualify_after_window {
4630            // WINDOW before QUALIFY (DuckDB style)
4631            if let Some(windows) = &select.windows {
4632                self.write_window_clause(windows)?;
4633            }
4634            if let Some(qualify) = &select.qualify {
4635                self.write_clause_condition("QUALIFY", &qualify.this)?;
4636            }
4637        } else {
4638            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4639            if let Some(qualify) = &select.qualify {
4640                self.write_clause_condition("QUALIFY", &qualify.this)?;
4641            }
4642            if let Some(windows) = &select.windows {
4643                self.write_window_clause(windows)?;
4644            }
4645        }
4646
4647        // DISTRIBUTE BY (Hive/Spark)
4648        if let Some(distribute_by) = &select.distribute_by {
4649            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4650        }
4651
4652        // CLUSTER BY (Hive/Spark)
4653        if let Some(cluster_by) = &select.cluster_by {
4654            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4655        }
4656
4657        // SORT BY (Hive/Spark - comes before ORDER BY)
4658        if let Some(sort_by) = &select.sort_by {
4659            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4660        }
4661
4662        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4663        if let Some(order_by) = &select.order_by {
4664            if self.config.pretty {
4665                // Output leading comments on their own lines before ORDER BY
4666                for comment in &order_by.comments {
4667                    self.write_newline();
4668                    self.write_indent();
4669                    self.write_formatted_comment(comment);
4670                }
4671            } else {
4672                for comment in &order_by.comments {
4673                    self.write_space();
4674                    self.write_formatted_comment(comment);
4675                }
4676            }
4677            let keyword = if order_by.siblings {
4678                "ORDER SIBLINGS BY"
4679            } else {
4680                "ORDER BY"
4681            };
4682            self.write_order_clause(keyword, &order_by.expressions)?;
4683        }
4684
4685        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4686        if select.order_by.is_none()
4687            && select.fetch.is_some()
4688            && matches!(
4689                self.config.dialect,
4690                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4691            )
4692        {
4693            if self.config.pretty {
4694                self.write_newline();
4695                self.write_indent();
4696            } else {
4697                self.write_space();
4698            }
4699            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4700        }
4701
4702        // LIMIT and OFFSET
4703        // PostgreSQL and others use: LIMIT count OFFSET offset
4704        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4705        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4706        let is_presto_like = matches!(
4707            self.config.dialect,
4708            Some(DialectType::Presto) | Some(DialectType::Trino)
4709        );
4710
4711        if is_presto_like && select.offset.is_some() {
4712            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4713            if let Some(offset) = &select.offset {
4714                if self.config.pretty {
4715                    self.write_newline();
4716                    self.write_indent();
4717                } else {
4718                    self.write_space();
4719                }
4720                self.write_keyword("OFFSET");
4721                self.write_space();
4722                self.write_limit_expr(&offset.this)?;
4723                if offset.rows == Some(true) {
4724                    self.write_space();
4725                    self.write_keyword("ROWS");
4726                }
4727            }
4728            if let Some(limit) = &select.limit {
4729                if self.config.pretty {
4730                    self.write_newline();
4731                    self.write_indent();
4732                } else {
4733                    self.write_space();
4734                }
4735                self.write_keyword("LIMIT");
4736                self.write_space();
4737                self.write_limit_expr(&limit.this)?;
4738                if limit.percent {
4739                    self.write_space();
4740                    self.write_keyword("PERCENT");
4741                }
4742                // Emit any comments that were captured from before the LIMIT keyword
4743                for comment in &limit.comments {
4744                    self.write(" ");
4745                    self.write_formatted_comment(comment);
4746                }
4747            }
4748        } else {
4749            // Check if FETCH will be converted to LIMIT (used for ordering)
4750            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4751                !fetch.percent
4752                    && !fetch.with_ties
4753                    && fetch.count.is_some()
4754                    && matches!(
4755                        self.config.dialect,
4756                        Some(DialectType::Spark)
4757                            | Some(DialectType::Hive)
4758                            | Some(DialectType::DuckDB)
4759                            | Some(DialectType::SQLite)
4760                            | Some(DialectType::MySQL)
4761                            | Some(DialectType::BigQuery)
4762                            | Some(DialectType::Databricks)
4763                            | Some(DialectType::StarRocks)
4764                            | Some(DialectType::Doris)
4765                            | Some(DialectType::Athena)
4766                            | Some(DialectType::ClickHouse)
4767                            | Some(DialectType::Redshift)
4768                    )
4769            });
4770
4771            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4772            if let Some(limit) = &select.limit {
4773                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4774                if !matches!(
4775                    self.config.dialect,
4776                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
4777                ) {
4778                    if self.config.pretty {
4779                        self.write_newline();
4780                        self.write_indent();
4781                    } else {
4782                        self.write_space();
4783                    }
4784                    self.write_keyword("LIMIT");
4785                    self.write_space();
4786                    self.write_limit_expr(&limit.this)?;
4787                    if limit.percent {
4788                        self.write_space();
4789                        self.write_keyword("PERCENT");
4790                    }
4791                    // Emit any comments that were captured from before the LIMIT keyword
4792                    for comment in &limit.comments {
4793                        self.write(" ");
4794                        self.write_formatted_comment(comment);
4795                    }
4796                }
4797            }
4798
4799            // Convert TOP to LIMIT for non-TOP dialects
4800            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4801                if let Some(top) = &select.top {
4802                    if !top.percent && !top.with_ties {
4803                        if self.config.pretty {
4804                            self.write_newline();
4805                            self.write_indent();
4806                        } else {
4807                            self.write_space();
4808                        }
4809                        self.write_keyword("LIMIT");
4810                        self.write_space();
4811                        self.generate_expression(&top.this)?;
4812                    }
4813                }
4814            }
4815
4816            // If FETCH will be converted to LIMIT and there's also OFFSET,
4817            // emit LIMIT from FETCH BEFORE the OFFSET
4818            if fetch_as_limit && select.offset.is_some() {
4819                if let Some(fetch) = &select.fetch {
4820                    if self.config.pretty {
4821                        self.write_newline();
4822                        self.write_indent();
4823                    } else {
4824                        self.write_space();
4825                    }
4826                    self.write_keyword("LIMIT");
4827                    self.write_space();
4828                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4829                }
4830            }
4831
4832            // OFFSET
4833            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4834            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4835            if let Some(offset) = &select.offset {
4836                if self.config.pretty {
4837                    self.write_newline();
4838                    self.write_indent();
4839                } else {
4840                    self.write_space();
4841                }
4842                if matches!(
4843                    self.config.dialect,
4844                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
4845                ) {
4846                    // SQL Server 2012+ OFFSET ... FETCH syntax
4847                    self.write_keyword("OFFSET");
4848                    self.write_space();
4849                    self.write_limit_expr(&offset.this)?;
4850                    self.write_space();
4851                    self.write_keyword("ROWS");
4852                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4853                    if let Some(limit) = &select.limit {
4854                        self.write_space();
4855                        self.write_keyword("FETCH NEXT");
4856                        self.write_space();
4857                        self.write_limit_expr(&limit.this)?;
4858                        self.write_space();
4859                        self.write_keyword("ROWS ONLY");
4860                    }
4861                } else {
4862                    self.write_keyword("OFFSET");
4863                    self.write_space();
4864                    self.write_limit_expr(&offset.this)?;
4865                    // Output ROWS keyword if it was in the original SQL
4866                    if offset.rows == Some(true) {
4867                        self.write_space();
4868                        self.write_keyword("ROWS");
4869                    }
4870                }
4871            }
4872        }
4873
4874        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4875        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4876            if let Some(limit_by) = &select.limit_by {
4877                if !limit_by.is_empty() {
4878                    self.write_space();
4879                    self.write_keyword("BY");
4880                    self.write_space();
4881                    for (i, expr) in limit_by.iter().enumerate() {
4882                        if i > 0 {
4883                            self.write(", ");
4884                        }
4885                        self.generate_expression(expr)?;
4886                    }
4887                }
4888            }
4889        }
4890
4891        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4892        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4893            if let Some(settings) = &select.settings {
4894                if self.config.pretty {
4895                    self.write_newline();
4896                    self.write_indent();
4897                } else {
4898                    self.write_space();
4899                }
4900                self.write_keyword("SETTINGS");
4901                self.write_space();
4902                for (i, expr) in settings.iter().enumerate() {
4903                    if i > 0 {
4904                        self.write(", ");
4905                    }
4906                    self.generate_expression(expr)?;
4907                }
4908            }
4909
4910            if let Some(format_expr) = &select.format {
4911                if self.config.pretty {
4912                    self.write_newline();
4913                    self.write_indent();
4914                } else {
4915                    self.write_space();
4916                }
4917                self.write_keyword("FORMAT");
4918                self.write_space();
4919                self.generate_expression(format_expr)?;
4920            }
4921        }
4922
4923        // FETCH FIRST/NEXT
4924        if let Some(fetch) = &select.fetch {
4925            // Check if we already emitted LIMIT from FETCH before OFFSET
4926            let fetch_already_as_limit = select.offset.is_some()
4927                && !fetch.percent
4928                && !fetch.with_ties
4929                && fetch.count.is_some()
4930                && matches!(
4931                    self.config.dialect,
4932                    Some(DialectType::Spark)
4933                        | Some(DialectType::Hive)
4934                        | Some(DialectType::DuckDB)
4935                        | Some(DialectType::SQLite)
4936                        | Some(DialectType::MySQL)
4937                        | Some(DialectType::BigQuery)
4938                        | Some(DialectType::Databricks)
4939                        | Some(DialectType::StarRocks)
4940                        | Some(DialectType::Doris)
4941                        | Some(DialectType::Athena)
4942                        | Some(DialectType::ClickHouse)
4943                        | Some(DialectType::Redshift)
4944                );
4945
4946            if fetch_already_as_limit {
4947                // Already emitted as LIMIT before OFFSET, skip
4948            } else {
4949                if self.config.pretty {
4950                    self.write_newline();
4951                    self.write_indent();
4952                } else {
4953                    self.write_space();
4954                }
4955
4956                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4957                let use_limit = !fetch.percent
4958                    && !fetch.with_ties
4959                    && fetch.count.is_some()
4960                    && matches!(
4961                        self.config.dialect,
4962                        Some(DialectType::Spark)
4963                            | Some(DialectType::Hive)
4964                            | Some(DialectType::DuckDB)
4965                            | Some(DialectType::SQLite)
4966                            | Some(DialectType::MySQL)
4967                            | Some(DialectType::BigQuery)
4968                            | Some(DialectType::Databricks)
4969                            | Some(DialectType::StarRocks)
4970                            | Some(DialectType::Doris)
4971                            | Some(DialectType::Athena)
4972                            | Some(DialectType::ClickHouse)
4973                            | Some(DialectType::Redshift)
4974                    );
4975
4976                if use_limit {
4977                    self.write_keyword("LIMIT");
4978                    self.write_space();
4979                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4980                } else {
4981                    self.write_keyword("FETCH");
4982                    self.write_space();
4983                    self.write_keyword(&fetch.direction);
4984                    if let Some(ref count) = fetch.count {
4985                        self.write_space();
4986                        self.generate_expression(count)?;
4987                    }
4988                    if fetch.percent {
4989                        self.write_space();
4990                        self.write_keyword("PERCENT");
4991                    }
4992                    if fetch.rows {
4993                        self.write_space();
4994                        self.write_keyword("ROWS");
4995                    }
4996                    if fetch.with_ties {
4997                        self.write_space();
4998                        self.write_keyword("WITH TIES");
4999                    } else {
5000                        self.write_space();
5001                        self.write_keyword("ONLY");
5002                    }
5003                }
5004            } // close fetch_already_as_limit else
5005        }
5006
5007        // SAMPLE / TABLESAMPLE
5008        if let Some(sample) = &select.sample {
5009            use crate::dialects::DialectType;
5010            if self.config.pretty {
5011                self.write_newline();
5012            } else {
5013                self.write_space();
5014            }
5015
5016            if sample.is_using_sample {
5017                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
5018                self.write_keyword("USING SAMPLE");
5019                self.generate_sample_body(sample)?;
5020            } else {
5021                self.write_keyword("TABLESAMPLE");
5022
5023                // Snowflake defaults to BERNOULLI when no explicit method is given
5024                let snowflake_bernoulli =
5025                    matches!(self.config.dialect, Some(DialectType::Snowflake))
5026                        && !sample.explicit_method;
5027                if snowflake_bernoulli {
5028                    self.write_space();
5029                    self.write_keyword("BERNOULLI");
5030                }
5031
5032                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
5033                if matches!(sample.method, SampleMethod::Bucket) {
5034                    self.write_space();
5035                    self.write("(");
5036                    self.write_keyword("BUCKET");
5037                    self.write_space();
5038                    if let Some(ref num) = sample.bucket_numerator {
5039                        self.generate_expression(num)?;
5040                    }
5041                    self.write_space();
5042                    self.write_keyword("OUT OF");
5043                    self.write_space();
5044                    if let Some(ref denom) = sample.bucket_denominator {
5045                        self.generate_expression(denom)?;
5046                    }
5047                    if let Some(ref field) = sample.bucket_field {
5048                        self.write_space();
5049                        self.write_keyword("ON");
5050                        self.write_space();
5051                        self.generate_expression(field)?;
5052                    }
5053                    self.write(")");
5054                } else if sample.unit_after_size {
5055                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
5056                    if sample.explicit_method && sample.method_before_size {
5057                        self.write_space();
5058                        match sample.method {
5059                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5060                            SampleMethod::System => self.write_keyword("SYSTEM"),
5061                            SampleMethod::Block => self.write_keyword("BLOCK"),
5062                            SampleMethod::Row => self.write_keyword("ROW"),
5063                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5064                            _ => {}
5065                        }
5066                    }
5067                    self.write(" (");
5068                    self.generate_expression(&sample.size)?;
5069                    self.write_space();
5070                    match sample.method {
5071                        SampleMethod::Percent => self.write_keyword("PERCENT"),
5072                        SampleMethod::Row => self.write_keyword("ROWS"),
5073                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
5074                        _ => {
5075                            self.write_keyword("PERCENT");
5076                        }
5077                    }
5078                    self.write(")");
5079                } else {
5080                    // Syntax: TABLESAMPLE METHOD (size)
5081                    self.write_space();
5082                    match sample.method {
5083                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5084                        SampleMethod::System => self.write_keyword("SYSTEM"),
5085                        SampleMethod::Block => self.write_keyword("BLOCK"),
5086                        SampleMethod::Row => self.write_keyword("ROW"),
5087                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
5088                        SampleMethod::Bucket => {}
5089                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5090                    }
5091                    self.write(" (");
5092                    self.generate_expression(&sample.size)?;
5093                    if matches!(sample.method, SampleMethod::Percent) {
5094                        self.write_space();
5095                        self.write_keyword("PERCENT");
5096                    }
5097                    self.write(")");
5098                }
5099            }
5100
5101            if let Some(seed) = &sample.seed {
5102                self.write_space();
5103                // Databricks/Spark use REPEATABLE, not SEED
5104                let use_seed = sample.use_seed_keyword
5105                    && !matches!(
5106                        self.config.dialect,
5107                        Some(crate::dialects::DialectType::Databricks)
5108                            | Some(crate::dialects::DialectType::Spark)
5109                    );
5110                if use_seed {
5111                    self.write_keyword("SEED");
5112                } else {
5113                    self.write_keyword("REPEATABLE");
5114                }
5115                self.write(" (");
5116                self.generate_expression(seed)?;
5117                self.write(")");
5118            }
5119        }
5120
5121        // FOR UPDATE/SHARE locks
5122        // Skip locking clauses for dialects that don't support them
5123        if self.config.locking_reads_supported {
5124            for lock in &select.locks {
5125                if self.config.pretty {
5126                    self.write_newline();
5127                    self.write_indent();
5128                } else {
5129                    self.write_space();
5130                }
5131                self.generate_lock(lock)?;
5132            }
5133        }
5134
5135        // FOR XML clause (T-SQL)
5136        if !select.for_xml.is_empty() {
5137            if self.config.pretty {
5138                self.write_newline();
5139                self.write_indent();
5140            } else {
5141                self.write_space();
5142            }
5143            self.write_keyword("FOR XML");
5144            for (i, opt) in select.for_xml.iter().enumerate() {
5145                if self.config.pretty {
5146                    if i > 0 {
5147                        self.write(",");
5148                    }
5149                    self.write_newline();
5150                    self.write_indent();
5151                    self.write("  "); // extra indent for options
5152                } else {
5153                    if i > 0 {
5154                        self.write(",");
5155                    }
5156                    self.write_space();
5157                }
5158                self.generate_for_xml_option(opt)?;
5159            }
5160        }
5161
5162        // FOR JSON clause (T-SQL)
5163        if !select.for_json.is_empty()
5164            && matches!(
5165                self.config.dialect,
5166                None | Some(DialectType::TSQL) | Some(DialectType::Fabric)
5167            )
5168        {
5169            if self.config.pretty {
5170                self.write_newline();
5171                self.write_indent();
5172            } else {
5173                self.write_space();
5174            }
5175            self.write_keyword("FOR JSON");
5176            for (i, opt) in select.for_json.iter().enumerate() {
5177                if self.config.pretty {
5178                    if i > 0 {
5179                        self.write(",");
5180                    }
5181                    self.write_newline();
5182                    self.write_indent();
5183                    self.write("  "); // extra indent for options
5184                } else {
5185                    if i > 0 {
5186                        self.write(",");
5187                    }
5188                    self.write_space();
5189                }
5190                self.generate_for_xml_option(opt)?;
5191            }
5192        }
5193
5194        // TSQL: OPTION clause
5195        if let Some(ref option) = select.option {
5196            if matches!(
5197                self.config.dialect,
5198                Some(crate::dialects::DialectType::TSQL)
5199                    | Some(crate::dialects::DialectType::Fabric)
5200            ) {
5201                self.write_space();
5202                self.write(option);
5203            }
5204        }
5205
5206        Ok(())
5207    }
5208
5209    /// Generate a single FOR XML option
5210    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5211        match opt {
5212            Expression::QueryOption(qo) => {
5213                // Extract the option name from Var
5214                if let Expression::Var(var) = &*qo.this {
5215                    self.write(&var.this);
5216                } else {
5217                    self.generate_expression(&qo.this)?;
5218                }
5219                // If there's an expression (like PATH('element')), output it in parens
5220                if let Some(expr) = &qo.expression {
5221                    self.write("(");
5222                    self.generate_expression(expr)?;
5223                    self.write(")");
5224                }
5225            }
5226            _ => {
5227                self.generate_expression(opt)?;
5228            }
5229        }
5230        Ok(())
5231    }
5232
5233    fn generate_with(&mut self, with: &With) -> Result<()> {
5234        use crate::dialects::DialectType;
5235
5236        // Output leading comments before WITH
5237        for comment in &with.leading_comments {
5238            self.write_formatted_comment(comment);
5239            self.write(" ");
5240        }
5241        self.write_keyword("WITH");
5242        if with.recursive && self.config.cte_recursive_keyword_required {
5243            self.write_space();
5244            self.write_keyword("RECURSIVE");
5245        }
5246        self.write_space();
5247
5248        // BigQuery doesn't support column aliases in CTE definitions
5249        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5250
5251        for (i, cte) in with.ctes.iter().enumerate() {
5252            if i > 0 {
5253                self.write(",");
5254                if self.config.pretty {
5255                    self.write_space();
5256                } else {
5257                    self.write(" ");
5258                }
5259            }
5260            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5261                self.generate_expression(&cte.this)?;
5262                self.write_space();
5263                self.write_keyword("AS");
5264                self.write_space();
5265                self.generate_identifier(&cte.alias)?;
5266                continue;
5267            }
5268            self.generate_identifier(&cte.alias)?;
5269            // Output CTE comments after alias name, before AS
5270            for comment in &cte.comments {
5271                self.write_space();
5272                self.write_formatted_comment(comment);
5273            }
5274            if !cte.columns.is_empty() && !skip_cte_columns {
5275                self.write("(");
5276                for (j, col) in cte.columns.iter().enumerate() {
5277                    if j > 0 {
5278                        self.write(", ");
5279                    }
5280                    self.generate_identifier(col)?;
5281                }
5282                self.write(")");
5283            }
5284            // USING KEY (columns) for DuckDB recursive CTEs
5285            if !cte.key_expressions.is_empty() {
5286                self.write_space();
5287                self.write_keyword("USING KEY");
5288                self.write(" (");
5289                for (i, key) in cte.key_expressions.iter().enumerate() {
5290                    if i > 0 {
5291                        self.write(", ");
5292                    }
5293                    self.generate_identifier(key)?;
5294                }
5295                self.write(")");
5296            }
5297            self.write_space();
5298            self.write_keyword("AS");
5299            // MATERIALIZED / NOT MATERIALIZED
5300            if let Some(materialized) = cte.materialized {
5301                self.write_space();
5302                if materialized {
5303                    self.write_keyword("MATERIALIZED");
5304                } else {
5305                    self.write_keyword("NOT MATERIALIZED");
5306                }
5307            }
5308            self.write(" (");
5309            if self.config.pretty {
5310                self.write_newline();
5311                self.indent_level += 1;
5312                self.write_indent();
5313            }
5314            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5315            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5316            let wrap_values_in_select = matches!(
5317                self.config.dialect,
5318                Some(DialectType::Spark) | Some(DialectType::Databricks)
5319            ) && matches!(&cte.this, Expression::Values(_));
5320
5321            if wrap_values_in_select {
5322                self.write_keyword("SELECT");
5323                self.write(" * ");
5324                self.write_keyword("FROM");
5325                self.write_space();
5326            }
5327            self.generate_expression(&cte.this)?;
5328            if self.config.pretty {
5329                self.write_newline();
5330                self.indent_level -= 1;
5331                self.write_indent();
5332            }
5333            self.write(")");
5334        }
5335
5336        // Generate SEARCH/CYCLE clause if present
5337        if let Some(search) = &with.search {
5338            self.write_space();
5339            self.generate_expression(search)?;
5340        }
5341
5342        Ok(())
5343    }
5344
5345    /// Generate joins with proper nesting structure for pretty printing.
5346    /// Deferred-condition joins "own" the non-deferred joins that follow them
5347    /// within the same nesting_group.
5348    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5349        let mut i = 0;
5350        while i < joins.len() {
5351            if joins[i].deferred_condition {
5352                let parent_group = joins[i].nesting_group;
5353
5354                // This join owns the following non-deferred joins in the same nesting_group
5355                // First output the join keyword and table (without condition)
5356                self.generate_join_without_condition(&joins[i])?;
5357
5358                // Find the range of child joins: same nesting_group and not deferred
5359                let child_start = i + 1;
5360                let mut child_end = child_start;
5361                while child_end < joins.len()
5362                    && !joins[child_end].deferred_condition
5363                    && joins[child_end].nesting_group == parent_group
5364                {
5365                    child_end += 1;
5366                }
5367
5368                // Output child joins with extra indentation
5369                if child_start < child_end {
5370                    self.indent_level += 1;
5371                    for j in child_start..child_end {
5372                        self.generate_join(&joins[j])?;
5373                    }
5374                    self.indent_level -= 1;
5375                }
5376
5377                // Output the deferred condition at the parent level
5378                self.generate_join_condition(&joins[i])?;
5379
5380                i = child_end;
5381            } else {
5382                // Regular join (no nesting)
5383                self.generate_join(&joins[i])?;
5384                i += 1;
5385            }
5386        }
5387        Ok(())
5388    }
5389
5390    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5391    /// Used for deferred-condition joins where the condition is output after child joins.
5392    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5393        // Save and temporarily clear the condition to prevent generate_join from outputting it
5394        // We achieve this by creating a modified copy
5395        let mut join_copy = join.clone();
5396        join_copy.on = None;
5397        join_copy.using = Vec::new();
5398        join_copy.deferred_condition = false;
5399        self.generate_join(&join_copy)
5400    }
5401
5402    fn generate_join(&mut self, join: &Join) -> Result<()> {
5403        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5404        if join.kind == JoinKind::Implicit {
5405            self.write(",");
5406            if self.config.pretty {
5407                self.write_newline();
5408                self.write_indent();
5409            } else {
5410                self.write_space();
5411            }
5412            self.generate_expression(&join.this)?;
5413            return Ok(());
5414        }
5415
5416        if self.config.pretty {
5417            self.write_newline();
5418            self.write_indent();
5419        } else {
5420            self.write_space();
5421        }
5422
5423        // Helper: format hint suffix (e.g., " LOOP" or "")
5424        // Only include join hints for dialects that support them
5425        let hint_str = if self.config.join_hints {
5426            join.join_hint
5427                .as_ref()
5428                .map(|h| format!(" {}", h))
5429                .unwrap_or_default()
5430        } else {
5431            String::new()
5432        };
5433
5434        let clickhouse_join_keyword =
5435            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5436                if let Some(hint) = &join.join_hint {
5437                    let mut global = false;
5438                    let mut strictness: Option<&'static str> = None;
5439                    for part in hint.split_whitespace() {
5440                        if part.eq_ignore_ascii_case("GLOBAL") {
5441                            global = true;
5442                        } else if part.eq_ignore_ascii_case("ALL") {
5443                            strictness = Some("ALL");
5444                        } else if part.eq_ignore_ascii_case("ANY") {
5445                            strictness = Some("ANY");
5446                        } else if part.eq_ignore_ascii_case("ASOF") {
5447                            strictness = Some("ASOF");
5448                        } else if part.eq_ignore_ascii_case("SEMI") {
5449                            strictness = Some("SEMI");
5450                        } else if part.eq_ignore_ascii_case("ANTI") {
5451                            strictness = Some("ANTI");
5452                        }
5453                    }
5454
5455                    if global || strictness.is_some() {
5456                        let join_type = match join.kind {
5457                            JoinKind::Left => {
5458                                if join.use_outer_keyword {
5459                                    "LEFT OUTER"
5460                                } else if join.use_inner_keyword {
5461                                    "LEFT INNER"
5462                                } else {
5463                                    "LEFT"
5464                                }
5465                            }
5466                            JoinKind::Right => {
5467                                if join.use_outer_keyword {
5468                                    "RIGHT OUTER"
5469                                } else if join.use_inner_keyword {
5470                                    "RIGHT INNER"
5471                                } else {
5472                                    "RIGHT"
5473                                }
5474                            }
5475                            JoinKind::Full => {
5476                                if join.use_outer_keyword {
5477                                    "FULL OUTER"
5478                                } else {
5479                                    "FULL"
5480                                }
5481                            }
5482                            JoinKind::Inner => {
5483                                if join.use_inner_keyword {
5484                                    "INNER"
5485                                } else {
5486                                    ""
5487                                }
5488                            }
5489                            _ => "",
5490                        };
5491
5492                        let mut parts = Vec::new();
5493                        if global {
5494                            parts.push("GLOBAL");
5495                        }
5496                        if !join_type.is_empty() {
5497                            parts.push(join_type);
5498                        }
5499                        if let Some(strict) = strictness {
5500                            parts.push(strict);
5501                        }
5502                        parts.push("JOIN");
5503                        Some(parts.join(" "))
5504                    } else {
5505                        None
5506                    }
5507                } else {
5508                    None
5509                }
5510            } else {
5511                None
5512            };
5513
5514        // Output any comments associated with this join
5515        // In pretty mode, comments go on their own line before the join keyword
5516        // In non-pretty mode, comments go inline before the join keyword
5517        if !join.comments.is_empty() {
5518            if self.config.pretty {
5519                // In pretty mode, go back before the newline+indent we just wrote
5520                // and output comments on their own lines
5521                // We need to output comments BEFORE the join keyword on separate lines
5522                // Trim the trailing newline+indent we already wrote
5523                let trimmed = self.output.trim_end().len();
5524                self.output.truncate(trimmed);
5525                for comment in &join.comments {
5526                    self.write_newline();
5527                    self.write_indent();
5528                    self.write_formatted_comment(comment);
5529                }
5530                self.write_newline();
5531                self.write_indent();
5532            } else {
5533                for comment in &join.comments {
5534                    self.write_formatted_comment(comment);
5535                    self.write_space();
5536                }
5537            }
5538        }
5539
5540        let directed_str = if join.directed { " DIRECTED" } else { "" };
5541
5542        if let Some(keyword) = clickhouse_join_keyword {
5543            self.write_keyword(&keyword);
5544        } else {
5545            match join.kind {
5546                JoinKind::Inner => {
5547                    if join.use_inner_keyword {
5548                        if hint_str.is_empty() && directed_str.is_empty() {
5549                            self.write_keyword("INNER JOIN");
5550                        } else {
5551                            self.write_keyword("INNER");
5552                            if !hint_str.is_empty() {
5553                                self.write_keyword(&hint_str);
5554                            }
5555                            if !directed_str.is_empty() {
5556                                self.write_keyword(directed_str);
5557                            }
5558                            self.write_keyword(" JOIN");
5559                        }
5560                    } else {
5561                        if !hint_str.is_empty() {
5562                            self.write_keyword(hint_str.trim());
5563                            self.write_keyword(" ");
5564                        }
5565                        if !directed_str.is_empty() {
5566                            self.write_keyword("DIRECTED ");
5567                        }
5568                        self.write_keyword("JOIN");
5569                    }
5570                }
5571                JoinKind::Left => {
5572                    if join.use_outer_keyword {
5573                        if hint_str.is_empty() && directed_str.is_empty() {
5574                            self.write_keyword("LEFT OUTER JOIN");
5575                        } else {
5576                            self.write_keyword("LEFT OUTER");
5577                            if !hint_str.is_empty() {
5578                                self.write_keyword(&hint_str);
5579                            }
5580                            if !directed_str.is_empty() {
5581                                self.write_keyword(directed_str);
5582                            }
5583                            self.write_keyword(" JOIN");
5584                        }
5585                    } else if join.use_inner_keyword {
5586                        if hint_str.is_empty() && directed_str.is_empty() {
5587                            self.write_keyword("LEFT INNER JOIN");
5588                        } else {
5589                            self.write_keyword("LEFT INNER");
5590                            if !hint_str.is_empty() {
5591                                self.write_keyword(&hint_str);
5592                            }
5593                            if !directed_str.is_empty() {
5594                                self.write_keyword(directed_str);
5595                            }
5596                            self.write_keyword(" JOIN");
5597                        }
5598                    } else {
5599                        if hint_str.is_empty() && directed_str.is_empty() {
5600                            self.write_keyword("LEFT JOIN");
5601                        } else {
5602                            self.write_keyword("LEFT");
5603                            if !hint_str.is_empty() {
5604                                self.write_keyword(&hint_str);
5605                            }
5606                            if !directed_str.is_empty() {
5607                                self.write_keyword(directed_str);
5608                            }
5609                            self.write_keyword(" JOIN");
5610                        }
5611                    }
5612                }
5613                JoinKind::Right => {
5614                    if join.use_outer_keyword {
5615                        if hint_str.is_empty() && directed_str.is_empty() {
5616                            self.write_keyword("RIGHT OUTER JOIN");
5617                        } else {
5618                            self.write_keyword("RIGHT OUTER");
5619                            if !hint_str.is_empty() {
5620                                self.write_keyword(&hint_str);
5621                            }
5622                            if !directed_str.is_empty() {
5623                                self.write_keyword(directed_str);
5624                            }
5625                            self.write_keyword(" JOIN");
5626                        }
5627                    } else if join.use_inner_keyword {
5628                        if hint_str.is_empty() && directed_str.is_empty() {
5629                            self.write_keyword("RIGHT INNER JOIN");
5630                        } else {
5631                            self.write_keyword("RIGHT INNER");
5632                            if !hint_str.is_empty() {
5633                                self.write_keyword(&hint_str);
5634                            }
5635                            if !directed_str.is_empty() {
5636                                self.write_keyword(directed_str);
5637                            }
5638                            self.write_keyword(" JOIN");
5639                        }
5640                    } else {
5641                        if hint_str.is_empty() && directed_str.is_empty() {
5642                            self.write_keyword("RIGHT JOIN");
5643                        } else {
5644                            self.write_keyword("RIGHT");
5645                            if !hint_str.is_empty() {
5646                                self.write_keyword(&hint_str);
5647                            }
5648                            if !directed_str.is_empty() {
5649                                self.write_keyword(directed_str);
5650                            }
5651                            self.write_keyword(" JOIN");
5652                        }
5653                    }
5654                }
5655                JoinKind::Full => {
5656                    if join.use_outer_keyword {
5657                        if hint_str.is_empty() && directed_str.is_empty() {
5658                            self.write_keyword("FULL OUTER JOIN");
5659                        } else {
5660                            self.write_keyword("FULL OUTER");
5661                            if !hint_str.is_empty() {
5662                                self.write_keyword(&hint_str);
5663                            }
5664                            if !directed_str.is_empty() {
5665                                self.write_keyword(directed_str);
5666                            }
5667                            self.write_keyword(" JOIN");
5668                        }
5669                    } else {
5670                        if hint_str.is_empty() && directed_str.is_empty() {
5671                            self.write_keyword("FULL JOIN");
5672                        } else {
5673                            self.write_keyword("FULL");
5674                            if !hint_str.is_empty() {
5675                                self.write_keyword(&hint_str);
5676                            }
5677                            if !directed_str.is_empty() {
5678                                self.write_keyword(directed_str);
5679                            }
5680                            self.write_keyword(" JOIN");
5681                        }
5682                    }
5683                }
5684                JoinKind::Outer => {
5685                    if directed_str.is_empty() {
5686                        self.write_keyword("OUTER JOIN");
5687                    } else {
5688                        self.write_keyword("OUTER");
5689                        self.write_keyword(directed_str);
5690                        self.write_keyword(" JOIN");
5691                    }
5692                }
5693                JoinKind::Cross => {
5694                    if directed_str.is_empty() {
5695                        self.write_keyword("CROSS JOIN");
5696                    } else {
5697                        self.write_keyword("CROSS");
5698                        self.write_keyword(directed_str);
5699                        self.write_keyword(" JOIN");
5700                    }
5701                }
5702                JoinKind::Natural => {
5703                    if join.use_inner_keyword {
5704                        if directed_str.is_empty() {
5705                            self.write_keyword("NATURAL INNER JOIN");
5706                        } else {
5707                            self.write_keyword("NATURAL INNER");
5708                            self.write_keyword(directed_str);
5709                            self.write_keyword(" JOIN");
5710                        }
5711                    } else {
5712                        if directed_str.is_empty() {
5713                            self.write_keyword("NATURAL JOIN");
5714                        } else {
5715                            self.write_keyword("NATURAL");
5716                            self.write_keyword(directed_str);
5717                            self.write_keyword(" JOIN");
5718                        }
5719                    }
5720                }
5721                JoinKind::NaturalLeft => {
5722                    if join.use_outer_keyword {
5723                        if directed_str.is_empty() {
5724                            self.write_keyword("NATURAL LEFT OUTER JOIN");
5725                        } else {
5726                            self.write_keyword("NATURAL LEFT OUTER");
5727                            self.write_keyword(directed_str);
5728                            self.write_keyword(" JOIN");
5729                        }
5730                    } else {
5731                        if directed_str.is_empty() {
5732                            self.write_keyword("NATURAL LEFT JOIN");
5733                        } else {
5734                            self.write_keyword("NATURAL LEFT");
5735                            self.write_keyword(directed_str);
5736                            self.write_keyword(" JOIN");
5737                        }
5738                    }
5739                }
5740                JoinKind::NaturalRight => {
5741                    if join.use_outer_keyword {
5742                        if directed_str.is_empty() {
5743                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
5744                        } else {
5745                            self.write_keyword("NATURAL RIGHT OUTER");
5746                            self.write_keyword(directed_str);
5747                            self.write_keyword(" JOIN");
5748                        }
5749                    } else {
5750                        if directed_str.is_empty() {
5751                            self.write_keyword("NATURAL RIGHT JOIN");
5752                        } else {
5753                            self.write_keyword("NATURAL RIGHT");
5754                            self.write_keyword(directed_str);
5755                            self.write_keyword(" JOIN");
5756                        }
5757                    }
5758                }
5759                JoinKind::NaturalFull => {
5760                    if join.use_outer_keyword {
5761                        if directed_str.is_empty() {
5762                            self.write_keyword("NATURAL FULL OUTER JOIN");
5763                        } else {
5764                            self.write_keyword("NATURAL FULL OUTER");
5765                            self.write_keyword(directed_str);
5766                            self.write_keyword(" JOIN");
5767                        }
5768                    } else {
5769                        if directed_str.is_empty() {
5770                            self.write_keyword("NATURAL FULL JOIN");
5771                        } else {
5772                            self.write_keyword("NATURAL FULL");
5773                            self.write_keyword(directed_str);
5774                            self.write_keyword(" JOIN");
5775                        }
5776                    }
5777                }
5778                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5779                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5780                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5781                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5782                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5783                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5784                JoinKind::CrossApply => {
5785                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL-like dialects
5786                    if matches!(
5787                        self.config.dialect,
5788                        Some(DialectType::TSQL) | Some(DialectType::Fabric) | None
5789                    ) {
5790                        self.write_keyword("CROSS APPLY");
5791                    } else {
5792                        self.write_keyword("INNER JOIN LATERAL");
5793                    }
5794                }
5795                JoinKind::OuterApply => {
5796                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL-like dialects
5797                    if matches!(
5798                        self.config.dialect,
5799                        Some(DialectType::TSQL) | Some(DialectType::Fabric) | None
5800                    ) {
5801                        self.write_keyword("OUTER APPLY");
5802                    } else {
5803                        self.write_keyword("LEFT JOIN LATERAL");
5804                    }
5805                }
5806                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5807                JoinKind::AsOfLeft => {
5808                    if join.use_outer_keyword {
5809                        self.write_keyword("ASOF LEFT OUTER JOIN");
5810                    } else {
5811                        self.write_keyword("ASOF LEFT JOIN");
5812                    }
5813                }
5814                JoinKind::AsOfRight => {
5815                    if join.use_outer_keyword {
5816                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5817                    } else {
5818                        self.write_keyword("ASOF RIGHT JOIN");
5819                    }
5820                }
5821                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5822                JoinKind::LeftLateral => {
5823                    if join.use_outer_keyword {
5824                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5825                    } else {
5826                        self.write_keyword("LEFT LATERAL JOIN");
5827                    }
5828                }
5829                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5830                JoinKind::Implicit => {
5831                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5832                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5833                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5834                    use crate::dialects::DialectType;
5835                    let is_cj_dialect = matches!(
5836                        self.config.dialect,
5837                        Some(DialectType::BigQuery)
5838                            | Some(DialectType::Hive)
5839                            | Some(DialectType::Spark)
5840                            | Some(DialectType::Databricks)
5841                    );
5842                    let source_is_same = self.config.source_dialect.is_some()
5843                        && self.config.source_dialect == self.config.dialect;
5844                    let source_is_cj = matches!(
5845                        self.config.source_dialect,
5846                        Some(DialectType::BigQuery)
5847                            | Some(DialectType::Hive)
5848                            | Some(DialectType::Spark)
5849                            | Some(DialectType::Databricks)
5850                    );
5851                    if is_cj_dialect
5852                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5853                    {
5854                        self.write_keyword("CROSS JOIN");
5855                    } else {
5856                        // Implicit join uses comma: FROM a, b
5857                        // We already wrote a space before the match, so replace with comma
5858                        // by removing trailing space and writing ", "
5859                        self.output.truncate(self.output.trim_end().len());
5860                        self.write(",");
5861                    }
5862                }
5863                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5864                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5865                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5866                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
5867            }
5868        }
5869
5870        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5871        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5872            match &join.this {
5873                Expression::Tuple(t) if t.expressions.is_empty() => {}
5874                Expression::Tuple(t) => {
5875                    self.write_space();
5876                    for (i, item) in t.expressions.iter().enumerate() {
5877                        if i > 0 {
5878                            self.write(", ");
5879                        }
5880                        self.generate_expression(item)?;
5881                    }
5882                }
5883                other => {
5884                    self.write_space();
5885                    self.generate_expression(other)?;
5886                }
5887            }
5888        } else {
5889            self.write_space();
5890            self.generate_expression(&join.this)?;
5891        }
5892
5893        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5894        if !join.deferred_condition {
5895            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5896            if let Some(match_cond) = &join.match_condition {
5897                self.write_space();
5898                self.write_keyword("MATCH_CONDITION");
5899                self.write(" (");
5900                self.generate_expression(match_cond)?;
5901                self.write(")");
5902            }
5903
5904            if let Some(on) = &join.on {
5905                if self.config.pretty {
5906                    self.write_newline();
5907                    self.indent_level += 1;
5908                    self.write_indent();
5909                    self.write_keyword("ON");
5910                    self.write_space();
5911                    self.generate_join_on_condition(on)?;
5912                    self.indent_level -= 1;
5913                } else {
5914                    self.write_space();
5915                    self.write_keyword("ON");
5916                    self.write_space();
5917                    self.generate_expression(on)?;
5918                }
5919            }
5920
5921            if !join.using.is_empty() {
5922                if self.config.pretty {
5923                    self.write_newline();
5924                    self.indent_level += 1;
5925                    self.write_indent();
5926                    self.write_keyword("USING");
5927                    self.write(" (");
5928                    for (i, col) in join.using.iter().enumerate() {
5929                        if i > 0 {
5930                            self.write(", ");
5931                        }
5932                        self.generate_identifier(col)?;
5933                    }
5934                    self.write(")");
5935                    self.indent_level -= 1;
5936                } else {
5937                    self.write_space();
5938                    self.write_keyword("USING");
5939                    self.write(" (");
5940                    for (i, col) in join.using.iter().enumerate() {
5941                        if i > 0 {
5942                            self.write(", ");
5943                        }
5944                        self.generate_identifier(col)?;
5945                    }
5946                    self.write(")");
5947                }
5948            }
5949        }
5950
5951        // Generate PIVOT/UNPIVOT expressions that follow this join
5952        for pivot in &join.pivots {
5953            self.write_space();
5954            self.generate_expression(pivot)?;
5955        }
5956
5957        Ok(())
5958    }
5959
5960    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5961    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5962        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5963        if let Some(match_cond) = &join.match_condition {
5964            self.write_space();
5965            self.write_keyword("MATCH_CONDITION");
5966            self.write(" (");
5967            self.generate_expression(match_cond)?;
5968            self.write(")");
5969        }
5970
5971        if let Some(on) = &join.on {
5972            if self.config.pretty {
5973                self.write_newline();
5974                self.indent_level += 1;
5975                self.write_indent();
5976                self.write_keyword("ON");
5977                self.write_space();
5978                // In pretty mode, split AND conditions onto separate lines
5979                self.generate_join_on_condition(on)?;
5980                self.indent_level -= 1;
5981            } else {
5982                self.write_space();
5983                self.write_keyword("ON");
5984                self.write_space();
5985                self.generate_expression(on)?;
5986            }
5987        }
5988
5989        if !join.using.is_empty() {
5990            if self.config.pretty {
5991                self.write_newline();
5992                self.indent_level += 1;
5993                self.write_indent();
5994                self.write_keyword("USING");
5995                self.write(" (");
5996                for (i, col) in join.using.iter().enumerate() {
5997                    if i > 0 {
5998                        self.write(", ");
5999                    }
6000                    self.generate_identifier(col)?;
6001                }
6002                self.write(")");
6003                self.indent_level -= 1;
6004            } else {
6005                self.write_space();
6006                self.write_keyword("USING");
6007                self.write(" (");
6008                for (i, col) in join.using.iter().enumerate() {
6009                    if i > 0 {
6010                        self.write(", ");
6011                    }
6012                    self.generate_identifier(col)?;
6013                }
6014                self.write(")");
6015            }
6016        }
6017
6018        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
6019        for pivot in &join.pivots {
6020            self.write_space();
6021            self.generate_expression(pivot)?;
6022        }
6023
6024        Ok(())
6025    }
6026
6027    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
6028    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
6029        if let Expression::And(and_op) = expr {
6030            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
6031                self.generate_expression(conditions[0])?;
6032                for condition in conditions.iter().skip(1) {
6033                    self.write_newline();
6034                    self.write_indent();
6035                    self.write_keyword("AND");
6036                    self.write_space();
6037                    self.generate_expression(condition)?;
6038                }
6039                return Ok(());
6040            }
6041        }
6042
6043        self.generate_expression(expr)
6044    }
6045
6046    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
6047        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
6048        self.write("(");
6049        self.generate_expression(&jt.left)?;
6050
6051        // Generate all joins
6052        for join in &jt.joins {
6053            self.generate_join(join)?;
6054        }
6055
6056        // Generate LATERAL VIEW clauses (Hive/Spark)
6057        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
6058            self.generate_lateral_view(lv, lv_idx)?;
6059        }
6060
6061        self.write(")");
6062
6063        // Alias
6064        if let Some(alias) = &jt.alias {
6065            self.write_space();
6066            self.write_keyword("AS");
6067            self.write_space();
6068            self.generate_identifier(alias)?;
6069        }
6070
6071        Ok(())
6072    }
6073
6074    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
6075        use crate::dialects::DialectType;
6076
6077        if self.config.pretty {
6078            self.write_newline();
6079            self.write_indent();
6080        } else {
6081            self.write_space();
6082        }
6083
6084        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
6085        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
6086        let use_lateral_join = matches!(
6087            self.config.dialect,
6088            Some(DialectType::PostgreSQL)
6089                | Some(DialectType::DuckDB)
6090                | Some(DialectType::Snowflake)
6091                | Some(DialectType::TSQL)
6092                | Some(DialectType::Presto)
6093                | Some(DialectType::Trino)
6094                | Some(DialectType::Athena)
6095        );
6096
6097        // Check if target dialect should use UNNEST instead of EXPLODE
6098        let use_unnest = matches!(
6099            self.config.dialect,
6100            Some(DialectType::DuckDB)
6101                | Some(DialectType::Presto)
6102                | Some(DialectType::Trino)
6103                | Some(DialectType::Athena)
6104        );
6105
6106        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
6107        let (is_posexplode, is_inline, func_args) = match &lv.this {
6108            Expression::Explode(uf) => {
6109                // Expression::Explode is the dedicated EXPLODE expression type
6110                (false, false, vec![uf.this.clone()])
6111            }
6112            Expression::Unnest(uf) => {
6113                let mut args = vec![uf.this.clone()];
6114                args.extend(uf.expressions.clone());
6115                (false, false, args)
6116            }
6117            Expression::Function(func) => {
6118                if func.name.eq_ignore_ascii_case("POSEXPLODE")
6119                    || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER")
6120                {
6121                    (true, false, func.args.clone())
6122                } else if func.name.eq_ignore_ascii_case("INLINE") {
6123                    (false, true, func.args.clone())
6124                } else if func.name.eq_ignore_ascii_case("EXPLODE")
6125                    || func.name.eq_ignore_ascii_case("EXPLODE_OUTER")
6126                {
6127                    (false, false, func.args.clone())
6128                } else {
6129                    (false, false, vec![])
6130                }
6131            }
6132            _ => (false, false, vec![]),
6133        };
6134
6135        if use_lateral_join {
6136            // Convert to CROSS JOIN for PostgreSQL-like dialects
6137            if lv.outer {
6138                self.write_keyword("LEFT JOIN LATERAL");
6139            } else {
6140                self.write_keyword("CROSS JOIN");
6141            }
6142            self.write_space();
6143
6144            if use_unnest && !func_args.is_empty() {
6145                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
6146                // For DuckDB, also convert ARRAY(y) -> [y]
6147                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6148                    // DuckDB: ARRAY(y) -> [y]
6149                    func_args
6150                        .iter()
6151                        .map(|a| {
6152                            if let Expression::Function(ref f) = a {
6153                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
6154                                    return Expression::ArrayFunc(Box::new(
6155                                        crate::expressions::ArrayConstructor {
6156                                            expressions: f.args.clone(),
6157                                            bracket_notation: true,
6158                                            use_list_keyword: false,
6159                                        },
6160                                    ));
6161                                }
6162                            }
6163                            a.clone()
6164                        })
6165                        .collect::<Vec<_>>()
6166                } else if matches!(
6167                    self.config.dialect,
6168                    Some(DialectType::Presto)
6169                        | Some(DialectType::Trino)
6170                        | Some(DialectType::Athena)
6171                ) {
6172                    // Presto: ARRAY(y) -> ARRAY[y]
6173                    func_args
6174                        .iter()
6175                        .map(|a| {
6176                            if let Expression::Function(ref f) = a {
6177                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
6178                                    return Expression::ArrayFunc(Box::new(
6179                                        crate::expressions::ArrayConstructor {
6180                                            expressions: f.args.clone(),
6181                                            bracket_notation: true,
6182                                            use_list_keyword: false,
6183                                        },
6184                                    ));
6185                                }
6186                            }
6187                            a.clone()
6188                        })
6189                        .collect::<Vec<_>>()
6190                } else {
6191                    func_args
6192                };
6193
6194                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
6195                if is_posexplode {
6196                    self.write_keyword("LATERAL");
6197                    self.write(" (");
6198                    self.write_keyword("SELECT");
6199                    self.write_space();
6200
6201                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
6202                    // column_aliases[0] is the position column, rest are data columns
6203                    let pos_alias = if !lv.column_aliases.is_empty() {
6204                        lv.column_aliases[0].clone()
6205                    } else {
6206                        Identifier::new("pos")
6207                    };
6208                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
6209                        lv.column_aliases[1..].to_vec()
6210                    } else {
6211                        vec![Identifier::new("col")]
6212                    };
6213
6214                    // pos - 1 AS pos
6215                    self.generate_identifier(&pos_alias)?;
6216                    self.write(" - 1");
6217                    self.write_space();
6218                    self.write_keyword("AS");
6219                    self.write_space();
6220                    self.generate_identifier(&pos_alias)?;
6221
6222                    // , col [, key, value ...]
6223                    for data_col in &data_aliases {
6224                        self.write(", ");
6225                        self.generate_identifier(data_col)?;
6226                    }
6227
6228                    self.write_space();
6229                    self.write_keyword("FROM");
6230                    self.write_space();
6231                    self.write_keyword("UNNEST");
6232                    self.write("(");
6233                    for (i, arg) in unnest_args.iter().enumerate() {
6234                        if i > 0 {
6235                            self.write(", ");
6236                        }
6237                        self.generate_expression(arg)?;
6238                    }
6239                    self.write(")");
6240                    self.write_space();
6241                    self.write_keyword("WITH ORDINALITY");
6242                    self.write_space();
6243                    self.write_keyword("AS");
6244                    self.write_space();
6245
6246                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6247                    let table_alias_ident = lv
6248                        .table_alias
6249                        .clone()
6250                        .unwrap_or_else(|| Identifier::new("t"));
6251                    self.generate_identifier(&table_alias_ident)?;
6252                    self.write("(");
6253                    for (i, data_col) in data_aliases.iter().enumerate() {
6254                        if i > 0 {
6255                            self.write(", ");
6256                        }
6257                        self.generate_identifier(data_col)?;
6258                    }
6259                    self.write(", ");
6260                    self.generate_identifier(&pos_alias)?;
6261                    self.write("))");
6262                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6263                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6264                    self.write_keyword("LATERAL");
6265                    self.write(" (");
6266                    self.write_keyword("SELECT");
6267                    self.write_space();
6268                    self.write_keyword("UNNEST");
6269                    self.write("(");
6270                    for (i, arg) in unnest_args.iter().enumerate() {
6271                        if i > 0 {
6272                            self.write(", ");
6273                        }
6274                        self.generate_expression(arg)?;
6275                    }
6276                    self.write(", ");
6277                    self.write_keyword("max_depth");
6278                    self.write(" => 2))");
6279
6280                    // Add table and column aliases
6281                    if let Some(alias) = &lv.table_alias {
6282                        self.write_space();
6283                        self.write_keyword("AS");
6284                        self.write_space();
6285                        self.generate_identifier(alias)?;
6286                        if !lv.column_aliases.is_empty() {
6287                            self.write("(");
6288                            for (i, col) in lv.column_aliases.iter().enumerate() {
6289                                if i > 0 {
6290                                    self.write(", ");
6291                                }
6292                                self.generate_identifier(col)?;
6293                            }
6294                            self.write(")");
6295                        }
6296                    } else if !lv.column_aliases.is_empty() {
6297                        // Auto-generate alias like _u_N
6298                        self.write_space();
6299                        self.write_keyword("AS");
6300                        self.write_space();
6301                        self.write(&format!("_u_{}", lv_index));
6302                        self.write("(");
6303                        for (i, col) in lv.column_aliases.iter().enumerate() {
6304                            if i > 0 {
6305                                self.write(", ");
6306                            }
6307                            self.generate_identifier(col)?;
6308                        }
6309                        self.write(")");
6310                    }
6311                } else {
6312                    self.write_keyword("UNNEST");
6313                    self.write("(");
6314                    for (i, arg) in unnest_args.iter().enumerate() {
6315                        if i > 0 {
6316                            self.write(", ");
6317                        }
6318                        self.generate_expression(arg)?;
6319                    }
6320                    self.write(")");
6321
6322                    // Add table and column aliases for non-POSEXPLODE
6323                    if let Some(alias) = &lv.table_alias {
6324                        self.write_space();
6325                        self.write_keyword("AS");
6326                        self.write_space();
6327                        self.generate_identifier(alias)?;
6328                        if !lv.column_aliases.is_empty() {
6329                            self.write("(");
6330                            for (i, col) in lv.column_aliases.iter().enumerate() {
6331                                if i > 0 {
6332                                    self.write(", ");
6333                                }
6334                                self.generate_identifier(col)?;
6335                            }
6336                            self.write(")");
6337                        }
6338                    } else if !lv.column_aliases.is_empty() {
6339                        self.write_space();
6340                        self.write_keyword("AS");
6341                        self.write(" t(");
6342                        for (i, col) in lv.column_aliases.iter().enumerate() {
6343                            if i > 0 {
6344                                self.write(", ");
6345                            }
6346                            self.generate_identifier(col)?;
6347                        }
6348                        self.write(")");
6349                    }
6350                }
6351            } else {
6352                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6353                if !lv.outer {
6354                    self.write_keyword("LATERAL");
6355                    self.write_space();
6356                }
6357                self.generate_expression(&lv.this)?;
6358
6359                // Add table and column aliases
6360                if let Some(alias) = &lv.table_alias {
6361                    self.write_space();
6362                    self.write_keyword("AS");
6363                    self.write_space();
6364                    self.generate_identifier(alias)?;
6365                    if !lv.column_aliases.is_empty() {
6366                        self.write("(");
6367                        for (i, col) in lv.column_aliases.iter().enumerate() {
6368                            if i > 0 {
6369                                self.write(", ");
6370                            }
6371                            self.generate_identifier(col)?;
6372                        }
6373                        self.write(")");
6374                    }
6375                } else if !lv.column_aliases.is_empty() {
6376                    self.write_space();
6377                    self.write_keyword("AS");
6378                    self.write(" t(");
6379                    for (i, col) in lv.column_aliases.iter().enumerate() {
6380                        if i > 0 {
6381                            self.write(", ");
6382                        }
6383                        self.generate_identifier(col)?;
6384                    }
6385                    self.write(")");
6386                }
6387            }
6388
6389            // For LEFT JOIN LATERAL, need ON TRUE
6390            if lv.outer {
6391                self.write_space();
6392                self.write_keyword("ON TRUE");
6393            }
6394        } else {
6395            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6396            self.write_keyword("LATERAL VIEW");
6397            if lv.outer {
6398                self.write_space();
6399                self.write_keyword("OUTER");
6400            }
6401            if self.config.pretty {
6402                self.write_newline();
6403                self.write_indent();
6404            } else {
6405                self.write_space();
6406            }
6407            self.generate_expression(&lv.this)?;
6408
6409            // Table alias
6410            if let Some(alias) = &lv.table_alias {
6411                self.write_space();
6412                self.generate_identifier(alias)?;
6413            }
6414
6415            // Column aliases
6416            if !lv.column_aliases.is_empty() {
6417                self.write_space();
6418                self.write_keyword("AS");
6419                self.write_space();
6420                for (i, col) in lv.column_aliases.iter().enumerate() {
6421                    if i > 0 {
6422                        self.write(", ");
6423                    }
6424                    self.generate_identifier(col)?;
6425                }
6426            }
6427        }
6428
6429        Ok(())
6430    }
6431
6432    fn should_wrap_set_operation_modifiers(
6433        &self,
6434        order_by: &Option<OrderBy>,
6435        limit: &Option<Box<Expression>>,
6436        offset: &Option<Box<Expression>>,
6437    ) -> bool {
6438        let has_row_limit = limit.is_some() || offset.is_some();
6439        let has_emulated_null_ordering = order_by.as_ref().map_or(false, |order_by| {
6440            order_by
6441                .expressions
6442                .iter()
6443                .any(|ordered| ordered.nulls_first.is_some())
6444        });
6445
6446        (has_row_limit || has_emulated_null_ordering)
6447            && matches!(
6448                self.config.dialect,
6449                Some(DialectType::TSQL) | Some(DialectType::Fabric)
6450            )
6451    }
6452
6453    fn generate_tsql_wrapped_set_operation(
6454        &mut self,
6455        inner: Expression,
6456        with: Option<With>,
6457        order_by: Option<OrderBy>,
6458        limit: Option<Box<Expression>>,
6459        offset: Option<Box<Expression>>,
6460    ) -> Result<()> {
6461        let subquery = Subquery {
6462            this: inner,
6463            alias: Some(Identifier::new("_l_0".to_string())),
6464            column_aliases: Vec::new(),
6465            alias_explicit_as: true,
6466            alias_keyword: None,
6467            order_by: None,
6468            limit: None,
6469            offset: None,
6470            lateral: false,
6471            modifiers_inside: false,
6472            trailing_comments: Vec::new(),
6473            distribute_by: None,
6474            sort_by: None,
6475            cluster_by: None,
6476            inferred_type: None,
6477        };
6478
6479        let mut outer_select = Select {
6480            expressions: vec![Expression::Star(Star {
6481                table: None,
6482                except: None,
6483                replace: None,
6484                rename: None,
6485                trailing_comments: Vec::new(),
6486                span: None,
6487            })],
6488            from: Some(From {
6489                expressions: vec![Expression::Subquery(Box::new(subquery))],
6490            }),
6491            with,
6492            order_by,
6493            limit: limit.map(|limit| Limit {
6494                this: *limit,
6495                percent: false,
6496                comments: Vec::new(),
6497            }),
6498            offset: offset.map(|offset| Offset {
6499                this: *offset,
6500                rows: Some(true),
6501            }),
6502            ..Select::new()
6503        };
6504
6505        if outer_select.offset.is_some() && outer_select.order_by.is_none() {
6506            outer_select.order_by = Some(Self::dummy_tsql_order_by());
6507        }
6508
6509        self.generate_select(&outer_select)
6510    }
6511
6512    fn dummy_tsql_order_by() -> OrderBy {
6513        let null_select = Expression::Select(Box::new(Select {
6514            expressions: vec![Expression::Null(Null)],
6515            ..Select::new()
6516        }));
6517
6518        OrderBy {
6519            expressions: vec![Ordered {
6520                this: Expression::Subquery(Box::new(Subquery {
6521                    this: null_select,
6522                    alias: None,
6523                    column_aliases: Vec::new(),
6524                    alias_explicit_as: false,
6525                    alias_keyword: None,
6526                    order_by: None,
6527                    limit: None,
6528                    offset: None,
6529                    lateral: false,
6530                    modifiers_inside: false,
6531                    trailing_comments: Vec::new(),
6532                    distribute_by: None,
6533                    sort_by: None,
6534                    cluster_by: None,
6535                    inferred_type: None,
6536                })),
6537                desc: false,
6538                nulls_first: None,
6539                explicit_asc: false,
6540                with_fill: None,
6541            }],
6542            siblings: false,
6543            comments: Vec::new(),
6544        }
6545    }
6546
6547    fn generate_union(&mut self, outermost: &Union) -> Result<()> {
6548        if self.should_wrap_set_operation_modifiers(
6549            &outermost.order_by,
6550            &outermost.limit,
6551            &outermost.offset,
6552        ) {
6553            let mut inner = outermost.clone();
6554            let with = inner.with.take();
6555            let order_by = inner.order_by.take();
6556            let limit = inner.limit.take();
6557            let offset = inner.offset.take();
6558
6559            return self.generate_tsql_wrapped_set_operation(
6560                Expression::Union(Box::new(inner)),
6561                with,
6562                order_by,
6563                limit,
6564                offset,
6565            );
6566        }
6567
6568        // Collect the left-recursive chain of Union nodes iteratively.
6569        // This avoids stack overflow for deeply nested chains like
6570        // SELECT 1 UNION ALL SELECT 2 UNION ALL ... UNION ALL SELECT N
6571        // where the parser builds: Union(Union(Union(A, B), C), D)
6572        let mut chain: Vec<&Union> = vec![outermost];
6573        let mut leftmost: &Expression = &outermost.left;
6574        while let Expression::Union(inner) = leftmost {
6575            chain.push(inner);
6576            leftmost = &inner.left;
6577        }
6578        // chain[0] = outermost, chain[last] = innermost
6579        // leftmost = innermost.left (a non-Union expression, typically Select)
6580
6581        // WITH clause (only on outermost)
6582        if let Some(with) = &outermost.with {
6583            self.generate_with(with)?;
6584            self.write_space();
6585        }
6586
6587        // Generate the base (leftmost) expression
6588        self.generate_expression(leftmost)?;
6589
6590        // Generate each union step from innermost to outermost
6591        for union in chain.iter().rev() {
6592            self.generate_union_step(union)?;
6593        }
6594        Ok(())
6595    }
6596
6597    /// Generate a single UNION step: keyword, right expression, and trailing modifiers.
6598    fn generate_union_step(&mut self, union: &Union) -> Result<()> {
6599        if self.config.pretty {
6600            self.write_newline();
6601            self.write_indent();
6602        } else {
6603            self.write_space();
6604        }
6605
6606        // BigQuery set operation modifiers: [side] [kind] UNION
6607        if let Some(side) = &union.side {
6608            self.write_keyword(side);
6609            self.write_space();
6610        }
6611        if let Some(kind) = &union.kind {
6612            self.write_keyword(kind);
6613            self.write_space();
6614        }
6615
6616        self.write_keyword("UNION");
6617        if union.all {
6618            self.write_space();
6619            self.write_keyword("ALL");
6620        } else if union.distinct {
6621            self.write_space();
6622            self.write_keyword("DISTINCT");
6623        }
6624
6625        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6626        // DuckDB: BY NAME
6627        if union.corresponding || union.by_name {
6628            self.write_space();
6629            self.write_keyword("BY NAME");
6630        }
6631        if !union.on_columns.is_empty() {
6632            self.write_space();
6633            self.write_keyword("ON");
6634            self.write(" (");
6635            for (i, col) in union.on_columns.iter().enumerate() {
6636                if i > 0 {
6637                    self.write(", ");
6638                }
6639                self.generate_expression(col)?;
6640            }
6641            self.write(")");
6642        }
6643
6644        if self.config.pretty {
6645            self.write_newline();
6646            self.write_indent();
6647        } else {
6648            self.write_space();
6649        }
6650        self.generate_expression(&union.right)?;
6651        // ORDER BY, LIMIT, OFFSET for the set operation
6652        if let Some(order_by) = &union.order_by {
6653            if self.config.pretty {
6654                self.write_newline();
6655            } else {
6656                self.write_space();
6657            }
6658            self.write_keyword("ORDER BY");
6659            self.write_space();
6660            for (i, ordered) in order_by.expressions.iter().enumerate() {
6661                if i > 0 {
6662                    self.write(", ");
6663                }
6664                self.generate_ordered(ordered)?;
6665            }
6666        }
6667        if let Some(limit) = &union.limit {
6668            if self.config.pretty {
6669                self.write_newline();
6670            } else {
6671                self.write_space();
6672            }
6673            self.write_keyword("LIMIT");
6674            self.write_space();
6675            self.generate_expression(limit)?;
6676        }
6677        if let Some(offset) = &union.offset {
6678            if self.config.pretty {
6679                self.write_newline();
6680            } else {
6681                self.write_space();
6682            }
6683            self.write_keyword("OFFSET");
6684            self.write_space();
6685            self.generate_expression(offset)?;
6686        }
6687        // DISTRIBUTE BY (Hive/Spark)
6688        if let Some(distribute_by) = &union.distribute_by {
6689            self.write_space();
6690            self.write_keyword("DISTRIBUTE BY");
6691            self.write_space();
6692            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6693                if i > 0 {
6694                    self.write(", ");
6695                }
6696                self.generate_expression(expr)?;
6697            }
6698        }
6699        // SORT BY (Hive/Spark)
6700        if let Some(sort_by) = &union.sort_by {
6701            self.write_space();
6702            self.write_keyword("SORT BY");
6703            self.write_space();
6704            for (i, ord) in sort_by.expressions.iter().enumerate() {
6705                if i > 0 {
6706                    self.write(", ");
6707                }
6708                self.generate_ordered(ord)?;
6709            }
6710        }
6711        // CLUSTER BY (Hive/Spark)
6712        if let Some(cluster_by) = &union.cluster_by {
6713            self.write_space();
6714            self.write_keyword("CLUSTER BY");
6715            self.write_space();
6716            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6717                if i > 0 {
6718                    self.write(", ");
6719                }
6720                self.generate_ordered(ord)?;
6721            }
6722        }
6723        Ok(())
6724    }
6725
6726    fn generate_intersect(&mut self, outermost: &Intersect) -> Result<()> {
6727        if self.should_wrap_set_operation_modifiers(
6728            &outermost.order_by,
6729            &outermost.limit,
6730            &outermost.offset,
6731        ) {
6732            let mut inner = outermost.clone();
6733            let with = inner.with.take();
6734            let order_by = inner.order_by.take();
6735            let limit = inner.limit.take();
6736            let offset = inner.offset.take();
6737
6738            return self.generate_tsql_wrapped_set_operation(
6739                Expression::Intersect(Box::new(inner)),
6740                with,
6741                order_by,
6742                limit,
6743                offset,
6744            );
6745        }
6746
6747        // Collect the left-recursive chain iteratively to avoid stack overflow
6748        let mut chain: Vec<&Intersect> = vec![outermost];
6749        let mut leftmost: &Expression = &outermost.left;
6750        while let Expression::Intersect(inner) = leftmost {
6751            chain.push(inner);
6752            leftmost = &inner.left;
6753        }
6754
6755        if let Some(with) = &outermost.with {
6756            self.generate_with(with)?;
6757            self.write_space();
6758        }
6759
6760        self.generate_expression(leftmost)?;
6761
6762        for intersect in chain.iter().rev() {
6763            self.generate_intersect_step(intersect)?;
6764        }
6765        Ok(())
6766    }
6767
6768    /// Generate a single INTERSECT step: keyword, right expression, and trailing modifiers.
6769    fn generate_intersect_step(&mut self, intersect: &Intersect) -> Result<()> {
6770        if self.config.pretty {
6771            self.write_newline();
6772            self.write_indent();
6773        } else {
6774            self.write_space();
6775        }
6776
6777        // BigQuery set operation modifiers: [side] [kind] INTERSECT
6778        if let Some(side) = &intersect.side {
6779            self.write_keyword(side);
6780            self.write_space();
6781        }
6782        if let Some(kind) = &intersect.kind {
6783            self.write_keyword(kind);
6784            self.write_space();
6785        }
6786
6787        self.write_keyword("INTERSECT");
6788        if intersect.all {
6789            self.write_space();
6790            self.write_keyword("ALL");
6791        } else if intersect.distinct {
6792            self.write_space();
6793            self.write_keyword("DISTINCT");
6794        }
6795
6796        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6797        // DuckDB: BY NAME
6798        if intersect.corresponding || intersect.by_name {
6799            self.write_space();
6800            self.write_keyword("BY NAME");
6801        }
6802        if !intersect.on_columns.is_empty() {
6803            self.write_space();
6804            self.write_keyword("ON");
6805            self.write(" (");
6806            for (i, col) in intersect.on_columns.iter().enumerate() {
6807                if i > 0 {
6808                    self.write(", ");
6809                }
6810                self.generate_expression(col)?;
6811            }
6812            self.write(")");
6813        }
6814
6815        if self.config.pretty {
6816            self.write_newline();
6817            self.write_indent();
6818        } else {
6819            self.write_space();
6820        }
6821        self.generate_expression(&intersect.right)?;
6822        // ORDER BY, LIMIT, OFFSET for the set operation
6823        if let Some(order_by) = &intersect.order_by {
6824            if self.config.pretty {
6825                self.write_newline();
6826            } else {
6827                self.write_space();
6828            }
6829            self.write_keyword("ORDER BY");
6830            self.write_space();
6831            for (i, ordered) in order_by.expressions.iter().enumerate() {
6832                if i > 0 {
6833                    self.write(", ");
6834                }
6835                self.generate_ordered(ordered)?;
6836            }
6837        }
6838        if let Some(limit) = &intersect.limit {
6839            if self.config.pretty {
6840                self.write_newline();
6841            } else {
6842                self.write_space();
6843            }
6844            self.write_keyword("LIMIT");
6845            self.write_space();
6846            self.generate_expression(limit)?;
6847        }
6848        if let Some(offset) = &intersect.offset {
6849            if self.config.pretty {
6850                self.write_newline();
6851            } else {
6852                self.write_space();
6853            }
6854            self.write_keyword("OFFSET");
6855            self.write_space();
6856            self.generate_expression(offset)?;
6857        }
6858        // DISTRIBUTE BY (Hive/Spark)
6859        if let Some(distribute_by) = &intersect.distribute_by {
6860            self.write_space();
6861            self.write_keyword("DISTRIBUTE BY");
6862            self.write_space();
6863            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6864                if i > 0 {
6865                    self.write(", ");
6866                }
6867                self.generate_expression(expr)?;
6868            }
6869        }
6870        // SORT BY (Hive/Spark)
6871        if let Some(sort_by) = &intersect.sort_by {
6872            self.write_space();
6873            self.write_keyword("SORT BY");
6874            self.write_space();
6875            for (i, ord) in sort_by.expressions.iter().enumerate() {
6876                if i > 0 {
6877                    self.write(", ");
6878                }
6879                self.generate_ordered(ord)?;
6880            }
6881        }
6882        // CLUSTER BY (Hive/Spark)
6883        if let Some(cluster_by) = &intersect.cluster_by {
6884            self.write_space();
6885            self.write_keyword("CLUSTER BY");
6886            self.write_space();
6887            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6888                if i > 0 {
6889                    self.write(", ");
6890                }
6891                self.generate_ordered(ord)?;
6892            }
6893        }
6894        Ok(())
6895    }
6896
6897    fn generate_except(&mut self, outermost: &Except) -> Result<()> {
6898        if self.should_wrap_set_operation_modifiers(
6899            &outermost.order_by,
6900            &outermost.limit,
6901            &outermost.offset,
6902        ) {
6903            let mut inner = outermost.clone();
6904            let with = inner.with.take();
6905            let order_by = inner.order_by.take();
6906            let limit = inner.limit.take();
6907            let offset = inner.offset.take();
6908
6909            return self.generate_tsql_wrapped_set_operation(
6910                Expression::Except(Box::new(inner)),
6911                with,
6912                order_by,
6913                limit,
6914                offset,
6915            );
6916        }
6917
6918        // Collect the left-recursive chain iteratively to avoid stack overflow
6919        let mut chain: Vec<&Except> = vec![outermost];
6920        let mut leftmost: &Expression = &outermost.left;
6921        while let Expression::Except(inner) = leftmost {
6922            chain.push(inner);
6923            leftmost = &inner.left;
6924        }
6925
6926        if let Some(with) = &outermost.with {
6927            self.generate_with(with)?;
6928            self.write_space();
6929        }
6930
6931        self.generate_expression(leftmost)?;
6932
6933        for except in chain.iter().rev() {
6934            self.generate_except_step(except)?;
6935        }
6936        Ok(())
6937    }
6938
6939    /// Generate a single EXCEPT step: keyword, right expression, and trailing modifiers.
6940    fn generate_except_step(&mut self, except: &Except) -> Result<()> {
6941        use crate::dialects::DialectType;
6942
6943        if self.config.pretty {
6944            self.write_newline();
6945            self.write_indent();
6946        } else {
6947            self.write_space();
6948        }
6949
6950        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6951        if let Some(side) = &except.side {
6952            self.write_keyword(side);
6953            self.write_space();
6954        }
6955        if let Some(kind) = &except.kind {
6956            self.write_keyword(kind);
6957            self.write_space();
6958        }
6959
6960        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6961        match self.config.dialect {
6962            Some(DialectType::Oracle) if !except.all => {
6963                self.write_keyword("MINUS");
6964            }
6965            Some(DialectType::ClickHouse) => {
6966                self.write_keyword("EXCEPT");
6967                let preserve_all = self.config.source_dialect.is_none()
6968                    || matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
6969                if except.all && preserve_all {
6970                    self.write_space();
6971                    self.write_keyword("ALL");
6972                }
6973                if except.distinct {
6974                    self.write_space();
6975                    self.write_keyword("DISTINCT");
6976                }
6977            }
6978            Some(DialectType::BigQuery) => {
6979                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6980                self.write_keyword("EXCEPT");
6981                if except.all {
6982                    self.write_space();
6983                    self.write_keyword("ALL");
6984                } else {
6985                    self.write_space();
6986                    self.write_keyword("DISTINCT");
6987                }
6988            }
6989            _ => {
6990                self.write_keyword("EXCEPT");
6991                if except.all {
6992                    self.write_space();
6993                    self.write_keyword("ALL");
6994                } else if except.distinct {
6995                    self.write_space();
6996                    self.write_keyword("DISTINCT");
6997                }
6998            }
6999        }
7000
7001        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7002        // DuckDB: BY NAME
7003        if except.corresponding || except.by_name {
7004            self.write_space();
7005            self.write_keyword("BY NAME");
7006        }
7007        if !except.on_columns.is_empty() {
7008            self.write_space();
7009            self.write_keyword("ON");
7010            self.write(" (");
7011            for (i, col) in except.on_columns.iter().enumerate() {
7012                if i > 0 {
7013                    self.write(", ");
7014                }
7015                self.generate_expression(col)?;
7016            }
7017            self.write(")");
7018        }
7019
7020        if self.config.pretty {
7021            self.write_newline();
7022            self.write_indent();
7023        } else {
7024            self.write_space();
7025        }
7026        self.generate_expression(&except.right)?;
7027        // ORDER BY, LIMIT, OFFSET for the set operation
7028        if let Some(order_by) = &except.order_by {
7029            if self.config.pretty {
7030                self.write_newline();
7031            } else {
7032                self.write_space();
7033            }
7034            self.write_keyword("ORDER BY");
7035            self.write_space();
7036            for (i, ordered) in order_by.expressions.iter().enumerate() {
7037                if i > 0 {
7038                    self.write(", ");
7039                }
7040                self.generate_ordered(ordered)?;
7041            }
7042        }
7043        if let Some(limit) = &except.limit {
7044            if self.config.pretty {
7045                self.write_newline();
7046            } else {
7047                self.write_space();
7048            }
7049            self.write_keyword("LIMIT");
7050            self.write_space();
7051            self.generate_expression(limit)?;
7052        }
7053        if let Some(offset) = &except.offset {
7054            if self.config.pretty {
7055                self.write_newline();
7056            } else {
7057                self.write_space();
7058            }
7059            self.write_keyword("OFFSET");
7060            self.write_space();
7061            self.generate_expression(offset)?;
7062        }
7063        // DISTRIBUTE BY (Hive/Spark)
7064        if let Some(distribute_by) = &except.distribute_by {
7065            self.write_space();
7066            self.write_keyword("DISTRIBUTE BY");
7067            self.write_space();
7068            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7069                if i > 0 {
7070                    self.write(", ");
7071                }
7072                self.generate_expression(expr)?;
7073            }
7074        }
7075        // SORT BY (Hive/Spark)
7076        if let Some(sort_by) = &except.sort_by {
7077            self.write_space();
7078            self.write_keyword("SORT BY");
7079            self.write_space();
7080            for (i, ord) in sort_by.expressions.iter().enumerate() {
7081                if i > 0 {
7082                    self.write(", ");
7083                }
7084                self.generate_ordered(ord)?;
7085            }
7086        }
7087        // CLUSTER BY (Hive/Spark)
7088        if let Some(cluster_by) = &except.cluster_by {
7089            self.write_space();
7090            self.write_keyword("CLUSTER BY");
7091            self.write_space();
7092            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7093                if i > 0 {
7094                    self.write(", ");
7095                }
7096                self.generate_ordered(ord)?;
7097            }
7098        }
7099        Ok(())
7100    }
7101
7102    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
7103        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
7104        let prepend_query_cte = if insert.with.is_none() {
7105            use crate::dialects::DialectType;
7106            let should_prepend = matches!(
7107                self.config.dialect,
7108                Some(DialectType::TSQL)
7109                    | Some(DialectType::Fabric)
7110                    | Some(DialectType::Spark)
7111                    | Some(DialectType::Databricks)
7112                    | Some(DialectType::Hive)
7113            );
7114            if should_prepend {
7115                if let Some(Expression::Select(select)) = &insert.query {
7116                    select.with.clone()
7117                } else {
7118                    None
7119                }
7120            } else {
7121                None
7122            }
7123        } else {
7124            None
7125        };
7126
7127        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
7128        if let Some(with) = &insert.with {
7129            self.generate_with(with)?;
7130            self.write_space();
7131        } else if let Some(with) = &prepend_query_cte {
7132            self.generate_with(with)?;
7133            self.write_space();
7134        }
7135
7136        // Output leading comments before INSERT
7137        for comment in &insert.leading_comments {
7138            self.write_formatted_comment(comment);
7139            self.write(" ");
7140        }
7141
7142        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
7143        if let Some(dir) = &insert.directory {
7144            self.write_keyword("INSERT OVERWRITE");
7145            if dir.local {
7146                self.write_space();
7147                self.write_keyword("LOCAL");
7148            }
7149            self.write_space();
7150            self.write_keyword("DIRECTORY");
7151            self.write_space();
7152            self.write("'");
7153            self.write(&dir.path);
7154            self.write("'");
7155
7156            // ROW FORMAT clause
7157            if let Some(row_format) = &dir.row_format {
7158                self.write_space();
7159                self.write_keyword("ROW FORMAT");
7160                if row_format.delimited {
7161                    self.write_space();
7162                    self.write_keyword("DELIMITED");
7163                }
7164                if let Some(val) = &row_format.fields_terminated_by {
7165                    self.write_space();
7166                    self.write_keyword("FIELDS TERMINATED BY");
7167                    self.write_space();
7168                    self.generate_string_literal(val)?;
7169                }
7170                if let Some(val) = &row_format.collection_items_terminated_by {
7171                    self.write_space();
7172                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
7173                    self.write_space();
7174                    self.write("'");
7175                    self.write(val);
7176                    self.write("'");
7177                }
7178                if let Some(val) = &row_format.map_keys_terminated_by {
7179                    self.write_space();
7180                    self.write_keyword("MAP KEYS TERMINATED BY");
7181                    self.write_space();
7182                    self.write("'");
7183                    self.write(val);
7184                    self.write("'");
7185                }
7186                if let Some(val) = &row_format.lines_terminated_by {
7187                    self.write_space();
7188                    self.write_keyword("LINES TERMINATED BY");
7189                    self.write_space();
7190                    self.write("'");
7191                    self.write(val);
7192                    self.write("'");
7193                }
7194                if let Some(val) = &row_format.null_defined_as {
7195                    self.write_space();
7196                    self.write_keyword("NULL DEFINED AS");
7197                    self.write_space();
7198                    self.write("'");
7199                    self.write(val);
7200                    self.write("'");
7201                }
7202            }
7203
7204            // STORED AS clause
7205            if let Some(format) = &dir.stored_as {
7206                self.write_space();
7207                self.write_keyword("STORED AS");
7208                self.write_space();
7209                self.write_keyword(format);
7210            }
7211
7212            // Query (SELECT statement)
7213            if let Some(query) = &insert.query {
7214                self.write_space();
7215                self.generate_expression(query)?;
7216            }
7217
7218            return Ok(());
7219        }
7220
7221        if insert.is_replace {
7222            // MySQL/SQLite REPLACE INTO statement
7223            self.write_keyword("REPLACE INTO");
7224        } else if insert.overwrite {
7225            // Use dialect-specific INSERT OVERWRITE format
7226            self.write_keyword("INSERT");
7227            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7228            if let Some(ref hint) = insert.hint {
7229                self.generate_hint(hint)?;
7230            }
7231            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
7232        } else if let Some(ref action) = insert.conflict_action {
7233            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
7234            self.write_keyword("INSERT OR");
7235            self.write_space();
7236            self.write_keyword(action);
7237            self.write_space();
7238            self.write_keyword("INTO");
7239        } else if insert.ignore {
7240            // MySQL INSERT IGNORE syntax
7241            self.write_keyword("INSERT IGNORE INTO");
7242        } else {
7243            self.write_keyword("INSERT");
7244            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7245            if let Some(ref hint) = insert.hint {
7246                self.generate_hint(hint)?;
7247            }
7248            self.write_space();
7249            self.write_keyword("INTO");
7250        }
7251        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
7252        if let Some(ref func) = insert.function_target {
7253            self.write_space();
7254            self.write_keyword("FUNCTION");
7255            self.write_space();
7256            self.generate_expression(func)?;
7257        } else {
7258            self.write_space();
7259            self.generate_table(&insert.table)?;
7260        }
7261
7262        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
7263        if let Some(ref alias) = insert.alias {
7264            self.write_space();
7265            if insert.alias_explicit_as {
7266                self.write_keyword("AS");
7267                self.write_space();
7268            }
7269            self.generate_identifier(alias)?;
7270        }
7271
7272        // IF EXISTS clause (Hive)
7273        if insert.if_exists {
7274            self.write_space();
7275            self.write_keyword("IF EXISTS");
7276        }
7277
7278        // REPLACE WHERE clause (Databricks)
7279        if let Some(ref replace_where) = insert.replace_where {
7280            if self.config.pretty {
7281                self.write_newline();
7282                self.write_indent();
7283            } else {
7284                self.write_space();
7285            }
7286            self.write_keyword("REPLACE WHERE");
7287            self.write_space();
7288            self.generate_expression(replace_where)?;
7289        }
7290
7291        // Generate PARTITION clause if present
7292        if !insert.partition.is_empty() {
7293            self.write_space();
7294            self.write_keyword("PARTITION");
7295            self.write("(");
7296            for (i, (col, val)) in insert.partition.iter().enumerate() {
7297                if i > 0 {
7298                    self.write(", ");
7299                }
7300                self.generate_identifier(col)?;
7301                if let Some(v) = val {
7302                    self.write(" = ");
7303                    self.generate_expression(v)?;
7304                }
7305            }
7306            self.write(")");
7307        }
7308
7309        // ClickHouse: PARTITION BY expr
7310        if let Some(ref partition_by) = insert.partition_by {
7311            self.write_space();
7312            self.write_keyword("PARTITION BY");
7313            self.write_space();
7314            self.generate_expression(partition_by)?;
7315        }
7316
7317        // ClickHouse: SETTINGS key = val, ...
7318        if !insert.settings.is_empty() {
7319            self.write_space();
7320            self.write_keyword("SETTINGS");
7321            self.write_space();
7322            for (i, setting) in insert.settings.iter().enumerate() {
7323                if i > 0 {
7324                    self.write(", ");
7325                }
7326                self.generate_expression(setting)?;
7327            }
7328        }
7329
7330        if !insert.columns.is_empty() {
7331            if insert.alias.is_some() && insert.alias_explicit_as {
7332                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
7333                self.write("(");
7334            } else {
7335                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
7336                self.write(" (");
7337            }
7338            for (i, col) in insert.columns.iter().enumerate() {
7339                if i > 0 {
7340                    self.write(", ");
7341                }
7342                self.generate_identifier(col)?;
7343            }
7344            self.write(")");
7345        }
7346
7347        // OUTPUT clause (TSQL)
7348        if let Some(ref output) = insert.output {
7349            self.generate_output_clause(output)?;
7350        }
7351
7352        // BY NAME modifier (DuckDB)
7353        if insert.by_name {
7354            self.write_space();
7355            self.write_keyword("BY NAME");
7356        }
7357
7358        if insert.default_values {
7359            self.write_space();
7360            self.write_keyword("DEFAULT VALUES");
7361        } else if let Some(query) = &insert.query {
7362            if self.config.pretty {
7363                self.write_newline();
7364            } else {
7365                self.write_space();
7366            }
7367            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
7368            if prepend_query_cte.is_some() {
7369                if let Expression::Select(select) = query {
7370                    let mut select_no_with = select.clone();
7371                    select_no_with.with = None;
7372                    self.generate_select(&select_no_with)?;
7373                } else {
7374                    self.generate_expression(query)?;
7375                }
7376            } else {
7377                self.generate_expression(query)?;
7378            }
7379        } else if !insert.values.is_empty() {
7380            if self.config.pretty {
7381                // Pretty printing: VALUES on new line, each tuple indented
7382                self.write_newline();
7383                self.write_keyword("VALUES");
7384                self.write_newline();
7385                self.indent_level += 1;
7386                for (i, row) in insert.values.iter().enumerate() {
7387                    if i > 0 {
7388                        self.write(",");
7389                        self.write_newline();
7390                    }
7391                    self.write_indent();
7392                    self.write("(");
7393                    for (j, val) in row.iter().enumerate() {
7394                        if j > 0 {
7395                            self.write(", ");
7396                        }
7397                        self.generate_expression(val)?;
7398                    }
7399                    self.write(")");
7400                }
7401                self.indent_level -= 1;
7402            } else {
7403                // Non-pretty: single line
7404                self.write_space();
7405                self.write_keyword("VALUES");
7406                for (i, row) in insert.values.iter().enumerate() {
7407                    if i > 0 {
7408                        self.write(",");
7409                    }
7410                    self.write(" (");
7411                    for (j, val) in row.iter().enumerate() {
7412                        if j > 0 {
7413                            self.write(", ");
7414                        }
7415                        self.generate_expression(val)?;
7416                    }
7417                    self.write(")");
7418                }
7419            }
7420        }
7421
7422        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
7423        if let Some(ref source) = insert.source {
7424            self.write_space();
7425            self.write_keyword("TABLE");
7426            self.write_space();
7427            self.generate_expression(source)?;
7428        }
7429
7430        // Source alias (MySQL: VALUES (...) AS new_data)
7431        if let Some(alias) = &insert.source_alias {
7432            self.write_space();
7433            self.write_keyword("AS");
7434            self.write_space();
7435            self.generate_identifier(alias)?;
7436        }
7437
7438        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
7439        if let Some(on_conflict) = &insert.on_conflict {
7440            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
7441                self.write_space();
7442                self.generate_expression(on_conflict)?;
7443            }
7444        }
7445
7446        // RETURNING clause
7447        if !insert.returning.is_empty() {
7448            self.write_space();
7449            self.write_keyword("RETURNING");
7450            self.write_space();
7451            for (i, expr) in insert.returning.iter().enumerate() {
7452                if i > 0 {
7453                    self.write(", ");
7454                }
7455                self.generate_expression(expr)?;
7456            }
7457        }
7458
7459        Ok(())
7460    }
7461
7462    fn generate_update(&mut self, update: &Update) -> Result<()> {
7463        // Output leading comments before UPDATE
7464        for comment in &update.leading_comments {
7465            self.write_formatted_comment(comment);
7466            self.write(" ");
7467        }
7468
7469        // WITH clause (CTEs)
7470        if let Some(ref with) = update.with {
7471            self.generate_with(with)?;
7472            self.write_space();
7473        }
7474
7475        self.write_keyword("UPDATE");
7476        if let Some(hint) = &update.hint {
7477            self.generate_hint(hint)?;
7478        }
7479        self.write_space();
7480        self.generate_table(&update.table)?;
7481
7482        let mysql_like_update_from = matches!(
7483            self.config.dialect,
7484            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
7485        ) && update.from_clause.is_some();
7486
7487        let mut set_pairs = update.set.clone();
7488
7489        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
7490        let mut pre_set_joins = update.table_joins.clone();
7491        if mysql_like_update_from {
7492            let target_name = update
7493                .table
7494                .alias
7495                .as_ref()
7496                .map(|a| a.name.clone())
7497                .unwrap_or_else(|| update.table.name.name.clone());
7498
7499            for (col, _) in &mut set_pairs {
7500                if !col.name.contains('.') {
7501                    col.name = format!("{}.{}", target_name, col.name);
7502                }
7503            }
7504
7505            if let Some(from_clause) = &update.from_clause {
7506                for table_expr in &from_clause.expressions {
7507                    pre_set_joins.push(crate::expressions::Join {
7508                        this: table_expr.clone(),
7509                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7510                            value: true,
7511                        })),
7512                        using: Vec::new(),
7513                        kind: crate::expressions::JoinKind::Inner,
7514                        use_inner_keyword: false,
7515                        use_outer_keyword: false,
7516                        deferred_condition: false,
7517                        join_hint: None,
7518                        match_condition: None,
7519                        pivots: Vec::new(),
7520                        comments: Vec::new(),
7521                        nesting_group: 0,
7522                        directed: false,
7523                    });
7524                }
7525            }
7526            for join in &update.from_joins {
7527                let mut join = join.clone();
7528                if join.on.is_none() && join.using.is_empty() {
7529                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7530                        value: true,
7531                    }));
7532                }
7533                pre_set_joins.push(join);
7534            }
7535        }
7536
7537        // Extra tables for multi-table UPDATE (MySQL syntax)
7538        for extra_table in &update.extra_tables {
7539            self.write(", ");
7540            self.generate_table(extra_table)?;
7541        }
7542
7543        // JOINs attached to the table list (MySQL multi-table syntax)
7544        for join in &pre_set_joins {
7545            // generate_join already adds a leading space
7546            self.generate_join(join)?;
7547        }
7548
7549        // Teradata: FROM clause comes before SET
7550        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
7551        if teradata_from_before_set && !mysql_like_update_from {
7552            if let Some(ref from_clause) = update.from_clause {
7553                self.write_space();
7554                self.write_keyword("FROM");
7555                self.write_space();
7556                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7557                    if i > 0 {
7558                        self.write(", ");
7559                    }
7560                    self.generate_expression(table_expr)?;
7561                }
7562            }
7563            for join in &update.from_joins {
7564                self.generate_join(join)?;
7565            }
7566        }
7567
7568        self.write_space();
7569        self.write_keyword("SET");
7570        self.write_space();
7571
7572        for (i, (col, val)) in set_pairs.iter().enumerate() {
7573            if i > 0 {
7574                self.write(", ");
7575            }
7576            self.generate_identifier(col)?;
7577            self.write(" = ");
7578            self.generate_expression(val)?;
7579        }
7580
7581        // OUTPUT clause (TSQL)
7582        if let Some(ref output) = update.output {
7583            self.generate_output_clause(output)?;
7584        }
7585
7586        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
7587        if !mysql_like_update_from && !teradata_from_before_set {
7588            if let Some(ref from_clause) = update.from_clause {
7589                self.write_space();
7590                self.write_keyword("FROM");
7591                self.write_space();
7592                // Generate each table in the FROM clause
7593                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7594                    if i > 0 {
7595                        self.write(", ");
7596                    }
7597                    self.generate_expression(table_expr)?;
7598                }
7599            }
7600        }
7601
7602        if !mysql_like_update_from && !teradata_from_before_set {
7603            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
7604            for join in &update.from_joins {
7605                self.generate_join(join)?;
7606            }
7607        }
7608
7609        if let Some(where_clause) = &update.where_clause {
7610            self.write_space();
7611            self.write_keyword("WHERE");
7612            self.write_space();
7613            self.generate_expression(&where_clause.this)?;
7614        }
7615
7616        // RETURNING clause
7617        if !update.returning.is_empty() {
7618            self.write_space();
7619            self.write_keyword("RETURNING");
7620            self.write_space();
7621            for (i, expr) in update.returning.iter().enumerate() {
7622                if i > 0 {
7623                    self.write(", ");
7624                }
7625                self.generate_expression(expr)?;
7626            }
7627        }
7628
7629        // ORDER BY clause (MySQL)
7630        if let Some(ref order_by) = update.order_by {
7631            self.write_space();
7632            self.generate_order_by(order_by)?;
7633        }
7634
7635        // LIMIT clause (MySQL)
7636        if let Some(ref limit) = update.limit {
7637            self.write_space();
7638            self.write_keyword("LIMIT");
7639            self.write_space();
7640            self.generate_expression(limit)?;
7641        }
7642
7643        Ok(())
7644    }
7645
7646    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
7647        // Output WITH clause if present
7648        if let Some(with) = &delete.with {
7649            self.generate_with(with)?;
7650            self.write_space();
7651        }
7652
7653        // Output leading comments before DELETE
7654        for comment in &delete.leading_comments {
7655            self.write_formatted_comment(comment);
7656            self.write(" ");
7657        }
7658
7659        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
7660        if !delete.tables.is_empty() && !delete.tables_from_using {
7661            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
7662            self.write_keyword("DELETE");
7663            if let Some(hint) = &delete.hint {
7664                self.generate_hint(hint)?;
7665            }
7666            self.write_space();
7667            for (i, tbl) in delete.tables.iter().enumerate() {
7668                if i > 0 {
7669                    self.write(", ");
7670                }
7671                self.generate_table(tbl)?;
7672            }
7673            // TSQL: OUTPUT clause between target table and FROM
7674            if let Some(ref output) = delete.output {
7675                self.generate_output_clause(output)?;
7676            }
7677            self.write_space();
7678            self.write_keyword("FROM");
7679            self.write_space();
7680            self.generate_table(&delete.table)?;
7681        } else if !delete.tables.is_empty() && delete.tables_from_using {
7682            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
7683            self.write_keyword("DELETE");
7684            if let Some(hint) = &delete.hint {
7685                self.generate_hint(hint)?;
7686            }
7687            self.write_space();
7688            self.write_keyword("FROM");
7689            self.write_space();
7690            for (i, tbl) in delete.tables.iter().enumerate() {
7691                if i > 0 {
7692                    self.write(", ");
7693                }
7694                self.generate_table(tbl)?;
7695            }
7696        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
7697            // BigQuery-style DELETE without FROM keyword
7698            self.write_keyword("DELETE");
7699            if let Some(hint) = &delete.hint {
7700                self.generate_hint(hint)?;
7701            }
7702            self.write_space();
7703            self.generate_table(&delete.table)?;
7704        } else {
7705            self.write_keyword("DELETE");
7706            if let Some(hint) = &delete.hint {
7707                self.generate_hint(hint)?;
7708            }
7709            self.write_space();
7710            self.write_keyword("FROM");
7711            self.write_space();
7712            self.generate_table(&delete.table)?;
7713        }
7714
7715        // ClickHouse: ON CLUSTER clause
7716        if let Some(ref on_cluster) = delete.on_cluster {
7717            self.write_space();
7718            self.generate_on_cluster(on_cluster)?;
7719        }
7720
7721        // FORCE INDEX hint (MySQL)
7722        if let Some(ref idx) = delete.force_index {
7723            self.write_space();
7724            self.write_keyword("FORCE INDEX");
7725            self.write(" (");
7726            self.write(idx);
7727            self.write(")");
7728        }
7729
7730        // Optional alias
7731        if let Some(ref alias) = delete.alias {
7732            self.write_space();
7733            if delete.alias_explicit_as
7734                || matches!(self.config.dialect, Some(DialectType::BigQuery))
7735            {
7736                self.write_keyword("AS");
7737                self.write_space();
7738            }
7739            self.generate_identifier(alias)?;
7740        }
7741
7742        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
7743        if !delete.tables_from_using {
7744            for join in &delete.joins {
7745                self.generate_join(join)?;
7746            }
7747        }
7748
7749        // USING clause (PostgreSQL/DuckDB/MySQL)
7750        if !delete.using.is_empty() {
7751            self.write_space();
7752            self.write_keyword("USING");
7753            for (i, table) in delete.using.iter().enumerate() {
7754                if i > 0 {
7755                    self.write(",");
7756                }
7757                self.write_space();
7758                // Check if the table has subquery hints (DuckDB USING with subquery)
7759                if !table.hints.is_empty() && table.name.is_empty() {
7760                    // Subquery in USING: (VALUES ...) AS alias(cols)
7761                    self.generate_expression(&table.hints[0])?;
7762                    if let Some(ref alias) = table.alias {
7763                        self.write_space();
7764                        if table.alias_explicit_as {
7765                            self.write_keyword("AS");
7766                            self.write_space();
7767                        }
7768                        self.generate_identifier(alias)?;
7769                        if !table.column_aliases.is_empty() {
7770                            self.write("(");
7771                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
7772                                if j > 0 {
7773                                    self.write(", ");
7774                                }
7775                                self.generate_identifier(col_alias)?;
7776                            }
7777                            self.write(")");
7778                        }
7779                    }
7780                } else {
7781                    self.generate_table(table)?;
7782                }
7783            }
7784        }
7785
7786        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
7787        if delete.tables_from_using {
7788            for join in &delete.joins {
7789                self.generate_join(join)?;
7790            }
7791        }
7792
7793        // OUTPUT clause (TSQL) - only if not already emitted in the early position
7794        let output_already_emitted =
7795            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
7796        if !output_already_emitted {
7797            if let Some(ref output) = delete.output {
7798                self.generate_output_clause(output)?;
7799            }
7800        }
7801
7802        if let Some(where_clause) = &delete.where_clause {
7803            self.write_space();
7804            self.write_keyword("WHERE");
7805            self.write_space();
7806            self.generate_expression(&where_clause.this)?;
7807        }
7808
7809        // ORDER BY clause (MySQL)
7810        if let Some(ref order_by) = delete.order_by {
7811            self.write_space();
7812            self.generate_order_by(order_by)?;
7813        }
7814
7815        // LIMIT clause (MySQL)
7816        if let Some(ref limit) = delete.limit {
7817            self.write_space();
7818            self.write_keyword("LIMIT");
7819            self.write_space();
7820            self.generate_expression(limit)?;
7821        }
7822
7823        // RETURNING clause (PostgreSQL)
7824        if !delete.returning.is_empty() {
7825            self.write_space();
7826            self.write_keyword("RETURNING");
7827            self.write_space();
7828            for (i, expr) in delete.returning.iter().enumerate() {
7829                if i > 0 {
7830                    self.write(", ");
7831                }
7832                self.generate_expression(expr)?;
7833            }
7834        }
7835
7836        Ok(())
7837    }
7838
7839    // ==================== DDL Generation ====================
7840
7841    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7842        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7843        // CREATE TABLE AS SELECT uses Trino (double quotes)
7844        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7845        let saved_athena_hive_context = self.athena_hive_context;
7846        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7847        if matches!(
7848            self.config.dialect,
7849            Some(crate::dialects::DialectType::Athena)
7850        ) {
7851            // Use Hive context if:
7852            // 1. It's an EXTERNAL table, OR
7853            // 2. There's no AS SELECT clause
7854            let is_external = ct
7855                .table_modifier
7856                .as_ref()
7857                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7858                .unwrap_or(false);
7859            let has_as_select = ct.as_select.is_some();
7860            self.athena_hive_context = is_external || !has_as_select;
7861        }
7862
7863        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7864        if matches!(
7865            self.config.dialect,
7866            Some(crate::dialects::DialectType::TSQL)
7867        ) {
7868            if let Some(ref query) = ct.as_select {
7869                // Output WITH CTE clause if present
7870                if let Some(with_cte) = &ct.with_cte {
7871                    self.generate_with(with_cte)?;
7872                    self.write_space();
7873                }
7874
7875                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7876                self.write_keyword("SELECT");
7877                self.write(" * ");
7878                self.write_keyword("INTO");
7879                self.write_space();
7880
7881                // If temporary, prefix with # for TSQL temp table
7882                if ct.temporary {
7883                    self.write("#");
7884                }
7885                self.generate_table(&ct.name)?;
7886
7887                self.write_space();
7888                self.write_keyword("FROM");
7889                self.write(" (");
7890                // For TSQL, add aliases to select columns to preserve column names
7891                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7892                self.generate_expression(&aliased_query)?;
7893                self.write(") ");
7894                self.write_keyword("AS");
7895                self.write(" temp");
7896                return Ok(());
7897            }
7898        }
7899
7900        // Output WITH CTE clause if present
7901        if let Some(with_cte) = &ct.with_cte {
7902            self.generate_with(with_cte)?;
7903            self.write_space();
7904        }
7905
7906        // Output leading comments before CREATE
7907        for comment in &ct.leading_comments {
7908            self.write_formatted_comment(comment);
7909            self.write(" ");
7910        }
7911        self.write_keyword("CREATE");
7912
7913        if ct.or_replace {
7914            self.write_space();
7915            self.write_keyword("OR REPLACE");
7916        }
7917
7918        if ct.temporary {
7919            self.write_space();
7920            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7921            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7922                self.write_keyword("GLOBAL TEMPORARY");
7923            } else {
7924                self.write_keyword("TEMPORARY");
7925            }
7926        }
7927
7928        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7929        let is_dictionary = ct
7930            .table_modifier
7931            .as_ref()
7932            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7933            .unwrap_or(false);
7934        if let Some(ref modifier) = ct.table_modifier {
7935            // TRANSIENT is Snowflake-specific - skip for other dialects
7936            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7937                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7938            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7939            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7940                || modifier.eq_ignore_ascii_case("SET")
7941                || modifier.eq_ignore_ascii_case("MULTISET")
7942                || modifier.to_ascii_uppercase().contains("VOLATILE")
7943                || modifier.to_ascii_uppercase().starts_with("SET ")
7944                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
7945            let skip_teradata =
7946                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7947            if !skip_transient && !skip_teradata {
7948                self.write_space();
7949                self.write_keyword(modifier);
7950            }
7951        }
7952
7953        if !is_dictionary {
7954            self.write_space();
7955            self.write_keyword("TABLE");
7956        }
7957
7958        if ct.if_not_exists {
7959            self.write_space();
7960            self.write_keyword("IF NOT EXISTS");
7961        }
7962
7963        self.write_space();
7964        self.generate_table(&ct.name)?;
7965
7966        // ClickHouse: UUID 'xxx' clause after table name
7967        if let Some(ref uuid) = ct.uuid {
7968            self.write_space();
7969            self.write_keyword("UUID");
7970            self.write(" '");
7971            self.write(uuid);
7972            self.write("'");
7973        }
7974
7975        // ClickHouse: ON CLUSTER clause
7976        if let Some(ref on_cluster) = ct.on_cluster {
7977            self.write_space();
7978            self.generate_on_cluster(on_cluster)?;
7979        }
7980
7981        // Teradata: options after table name before column list (comma-separated)
7982        if matches!(
7983            self.config.dialect,
7984            Some(crate::dialects::DialectType::Teradata)
7985        ) && !ct.teradata_post_name_options.is_empty()
7986        {
7987            for opt in &ct.teradata_post_name_options {
7988                self.write(", ");
7989                self.write(opt);
7990            }
7991        }
7992
7993        // Snowflake: COPY GRANTS clause
7994        if ct.copy_grants {
7995            self.write_space();
7996            self.write_keyword("COPY GRANTS");
7997        }
7998
7999        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
8000        if let Some(ref using_template) = ct.using_template {
8001            self.write_space();
8002            self.write_keyword("USING TEMPLATE");
8003            self.write_space();
8004            self.generate_expression(using_template)?;
8005            return Ok(());
8006        }
8007
8008        // ClickHouse uses CREATE TABLE target AS source [ENGINE ...] for table-structure copies.
8009        // When explicit columns or constraints are present, the source must be emitted
8010        // after the parenthesized schema: CREATE TABLE target (cols) AS source.
8011        if is_clickhouse {
8012            if let Some(ref clone_source) = ct.clone_source {
8013                if ct.columns.is_empty() && ct.constraints.is_empty() {
8014                    self.write_space();
8015                    self.write_keyword("AS");
8016                    self.write_space();
8017                    self.generate_table(clone_source)?;
8018                }
8019            }
8020        }
8021
8022        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
8023        if !is_clickhouse {
8024            if let Some(ref clone_source) = ct.clone_source {
8025                self.write_space();
8026                if ct.is_copy && self.config.supports_table_copy {
8027                    // BigQuery uses COPY
8028                    self.write_keyword("COPY");
8029                } else if ct.shallow_clone {
8030                    self.write_keyword("SHALLOW CLONE");
8031                } else if ct.deep_clone {
8032                    self.write_keyword("DEEP CLONE");
8033                } else {
8034                    self.write_keyword("CLONE");
8035                }
8036                self.write_space();
8037                self.generate_table(clone_source)?;
8038                // Generate AT/BEFORE time travel clause (stored as Raw expression)
8039                if let Some(ref at_clause) = ct.clone_at_clause {
8040                    self.write_space();
8041                    self.generate_expression(at_clause)?;
8042                }
8043                return Ok(());
8044            }
8045        }
8046
8047        // Handle PARTITION OF property
8048        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
8049        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
8050        if let Some(ref partition_of) = ct.partition_of {
8051            self.write_space();
8052
8053            // Extract the PartitionedOfProperty parts to generate them separately
8054            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
8055                // Output: PARTITION OF <table>
8056                self.write_keyword("PARTITION OF");
8057                self.write_space();
8058                self.generate_expression(&pop.this)?;
8059
8060                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
8061                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
8062                    self.write(" (");
8063                    let mut first = true;
8064                    for col in &ct.columns {
8065                        if !first {
8066                            self.write(", ");
8067                        }
8068                        first = false;
8069                        self.generate_column_def(col)?;
8070                    }
8071                    for constraint in &ct.constraints {
8072                        if !first {
8073                            self.write(", ");
8074                        }
8075                        first = false;
8076                        self.generate_table_constraint(constraint)?;
8077                    }
8078                    self.write(")");
8079                }
8080
8081                // Output partition bound spec: FOR VALUES ... or DEFAULT
8082                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
8083                    self.write_space();
8084                    self.write_keyword("FOR VALUES");
8085                    self.write_space();
8086                    self.generate_expression(&pop.expression)?;
8087                } else {
8088                    self.write_space();
8089                    self.write_keyword("DEFAULT");
8090                }
8091            } else {
8092                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
8093                self.generate_expression(partition_of)?;
8094
8095                // Output columns/constraints if present
8096                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
8097                    self.write(" (");
8098                    let mut first = true;
8099                    for col in &ct.columns {
8100                        if !first {
8101                            self.write(", ");
8102                        }
8103                        first = false;
8104                        self.generate_column_def(col)?;
8105                    }
8106                    for constraint in &ct.constraints {
8107                        if !first {
8108                            self.write(", ");
8109                        }
8110                        first = false;
8111                        self.generate_table_constraint(constraint)?;
8112                    }
8113                    self.write(")");
8114                }
8115            }
8116
8117            // Output table properties (e.g., PARTITION BY RANGE(population))
8118            for prop in &ct.properties {
8119                self.write_space();
8120                self.generate_expression(prop)?;
8121            }
8122
8123            return Ok(());
8124        }
8125
8126        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
8127        // This matches Python sqlglot's behavior for SQLite dialect
8128        self.sqlite_inline_pk_columns.clear();
8129        if matches!(
8130            self.config.dialect,
8131            Some(crate::dialects::DialectType::SQLite)
8132        ) {
8133            for constraint in &ct.constraints {
8134                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8135                    // Only inline if: single column, no constraint name, and column exists in table
8136                    if columns.len() == 1 && name.is_none() {
8137                        let pk_col_name = columns[0].name.to_ascii_lowercase();
8138                        // Check if this column exists in the table
8139                        if ct
8140                            .columns
8141                            .iter()
8142                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
8143                        {
8144                            self.sqlite_inline_pk_columns.insert(pk_col_name);
8145                        }
8146                    }
8147                }
8148            }
8149        }
8150
8151        // Output columns if present (even for CTAS with columns)
8152        if !ct.columns.is_empty() {
8153            if self.config.pretty {
8154                // Pretty print: each column on new line
8155                self.write(" (");
8156                self.write_newline();
8157                self.indent_level += 1;
8158                for (i, col) in ct.columns.iter().enumerate() {
8159                    if i > 0 {
8160                        self.write(",");
8161                        self.write_newline();
8162                    }
8163                    self.write_indent();
8164                    self.generate_column_def(col)?;
8165                }
8166                // Table constraints (skip inlined PRIMARY KEY for SQLite)
8167                for constraint in &ct.constraints {
8168                    // Skip single-column PRIMARY KEY that was inlined for SQLite
8169                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8170                        if columns.len() == 1
8171                            && name.is_none()
8172                            && self
8173                                .sqlite_inline_pk_columns
8174                                .contains(&columns[0].name.to_ascii_lowercase())
8175                        {
8176                            continue;
8177                        }
8178                    }
8179                    self.write(",");
8180                    self.write_newline();
8181                    self.write_indent();
8182                    self.generate_table_constraint(constraint)?;
8183                }
8184                self.indent_level -= 1;
8185                self.write_newline();
8186                self.write(")");
8187            } else {
8188                self.write(" (");
8189                for (i, col) in ct.columns.iter().enumerate() {
8190                    if i > 0 {
8191                        self.write(", ");
8192                    }
8193                    self.generate_column_def(col)?;
8194                }
8195                // Table constraints (skip inlined PRIMARY KEY for SQLite)
8196                let mut first_constraint = true;
8197                for constraint in &ct.constraints {
8198                    // Skip single-column PRIMARY KEY that was inlined for SQLite
8199                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8200                        if columns.len() == 1
8201                            && name.is_none()
8202                            && self
8203                                .sqlite_inline_pk_columns
8204                                .contains(&columns[0].name.to_ascii_lowercase())
8205                        {
8206                            continue;
8207                        }
8208                    }
8209                    if first_constraint {
8210                        self.write(", ");
8211                        first_constraint = false;
8212                    } else {
8213                        self.write(", ");
8214                    }
8215                    self.generate_table_constraint(constraint)?;
8216                }
8217                self.write(")");
8218            }
8219        } else if !ct.constraints.is_empty() {
8220            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
8221            let has_like_only = ct
8222                .constraints
8223                .iter()
8224                .all(|c| matches!(c, TableConstraint::Like { .. }));
8225            let has_tags_only = ct
8226                .constraints
8227                .iter()
8228                .all(|c| matches!(c, TableConstraint::Tags(_)));
8229            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
8230            // Most dialects: CREATE TABLE A LIKE B (no parens)
8231            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
8232            let is_pg_like = matches!(
8233                self.config.dialect,
8234                Some(crate::dialects::DialectType::PostgreSQL)
8235                    | Some(crate::dialects::DialectType::CockroachDB)
8236                    | Some(crate::dialects::DialectType::Materialize)
8237                    | Some(crate::dialects::DialectType::RisingWave)
8238                    | Some(crate::dialects::DialectType::Redshift)
8239                    | Some(crate::dialects::DialectType::Presto)
8240                    | Some(crate::dialects::DialectType::Trino)
8241                    | Some(crate::dialects::DialectType::Athena)
8242            );
8243            let use_parens = if has_like_only {
8244                is_pg_like
8245            } else {
8246                !has_tags_only
8247            };
8248            if self.config.pretty && use_parens {
8249                self.write(" (");
8250                self.write_newline();
8251                self.indent_level += 1;
8252                for (i, constraint) in ct.constraints.iter().enumerate() {
8253                    if i > 0 {
8254                        self.write(",");
8255                        self.write_newline();
8256                    }
8257                    self.write_indent();
8258                    self.generate_table_constraint(constraint)?;
8259                }
8260                self.indent_level -= 1;
8261                self.write_newline();
8262                self.write(")");
8263            } else {
8264                if use_parens {
8265                    self.write(" (");
8266                } else {
8267                    self.write_space();
8268                }
8269                for (i, constraint) in ct.constraints.iter().enumerate() {
8270                    if i > 0 {
8271                        self.write(", ");
8272                    }
8273                    self.generate_table_constraint(constraint)?;
8274                }
8275                if use_parens {
8276                    self.write(")");
8277                }
8278            }
8279        }
8280
8281        if is_clickhouse && (!ct.columns.is_empty() || !ct.constraints.is_empty()) {
8282            if let Some(ref clone_source) = ct.clone_source {
8283                self.write_space();
8284                self.write_keyword("AS");
8285                self.write_space();
8286                self.generate_table(clone_source)?;
8287            }
8288        }
8289
8290        // TSQL ON filegroup or ON filegroup (partition_column) clause
8291        if let Some(ref on_prop) = ct.on_property {
8292            self.write(" ");
8293            self.write_keyword("ON");
8294            self.write(" ");
8295            self.generate_expression(&on_prop.this)?;
8296        }
8297
8298        // BigQuery: WITH PARTITION COLUMNS (col_name col_type, ...)
8299        if !ct.with_partition_columns.is_empty() {
8300            if self.config.pretty {
8301                self.write_newline();
8302            } else {
8303                self.write_space();
8304            }
8305            self.write_keyword("WITH PARTITION COLUMNS");
8306            self.write(" (");
8307            if self.config.pretty {
8308                self.write_newline();
8309                self.indent_level += 1;
8310                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8311                    if i > 0 {
8312                        self.write(",");
8313                        self.write_newline();
8314                    }
8315                    self.write_indent();
8316                    self.generate_column_def(col)?;
8317                }
8318                self.indent_level -= 1;
8319                self.write_newline();
8320            } else {
8321                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8322                    if i > 0 {
8323                        self.write(", ");
8324                    }
8325                    self.generate_column_def(col)?;
8326                }
8327            }
8328            self.write(")");
8329        }
8330
8331        // BigQuery: WITH CONNECTION `project.region.connection`
8332        if let Some(ref conn) = ct.with_connection {
8333            if self.config.pretty {
8334                self.write_newline();
8335            } else {
8336                self.write_space();
8337            }
8338            self.write_keyword("WITH CONNECTION");
8339            self.write_space();
8340            self.generate_table(conn)?;
8341        }
8342
8343        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
8344        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
8345        if !is_clickhouse {
8346            for prop in &ct.properties {
8347                if let Expression::SchemaCommentProperty(_) = prop {
8348                    if self.config.pretty {
8349                        self.write_newline();
8350                    } else {
8351                        self.write_space();
8352                    }
8353                    self.generate_expression(prop)?;
8354                }
8355            }
8356        }
8357
8358        // WITH properties (output after columns if columns exist, otherwise before AS)
8359        if !ct.with_properties.is_empty() {
8360            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
8361            let is_snowflake_special_table = matches!(
8362                self.config.dialect,
8363                Some(crate::dialects::DialectType::Snowflake)
8364            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
8365                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
8366            if is_snowflake_special_table {
8367                for (key, value) in &ct.with_properties {
8368                    self.write_space();
8369                    self.write(key);
8370                    self.write("=");
8371                    self.write(value);
8372                }
8373            } else if self.config.pretty {
8374                self.write_newline();
8375                self.write_keyword("WITH");
8376                self.write(" (");
8377                self.write_newline();
8378                self.indent_level += 1;
8379                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8380                    if i > 0 {
8381                        self.write(",");
8382                        self.write_newline();
8383                    }
8384                    self.write_indent();
8385                    self.write(key);
8386                    self.write("=");
8387                    self.write(value);
8388                }
8389                self.indent_level -= 1;
8390                self.write_newline();
8391                self.write(")");
8392            } else {
8393                self.write_space();
8394                self.write_keyword("WITH");
8395                self.write(" (");
8396                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8397                    if i > 0 {
8398                        self.write(", ");
8399                    }
8400                    self.write(key);
8401                    self.write("=");
8402                    self.write(value);
8403                }
8404                self.write(")");
8405            }
8406        }
8407
8408        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
8409            if is_clickhouse && ct.as_select.is_some() {
8410                let mut pre = Vec::new();
8411                let mut post = Vec::new();
8412                for prop in &ct.properties {
8413                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
8414                        post.push(prop);
8415                    } else {
8416                        pre.push(prop);
8417                    }
8418                }
8419                (pre, post)
8420            } else {
8421                (ct.properties.iter().collect(), Vec::new())
8422            };
8423
8424        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
8425        for prop in pre_as_properties {
8426            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
8427            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
8428                continue;
8429            }
8430            if self.config.pretty {
8431                self.write_newline();
8432            } else {
8433                self.write_space();
8434            }
8435            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
8436            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
8437            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
8438            if let Expression::Properties(props) = prop {
8439                let is_hive_dialect = matches!(
8440                    self.config.dialect,
8441                    Some(crate::dialects::DialectType::Hive)
8442                        | Some(crate::dialects::DialectType::Spark)
8443                        | Some(crate::dialects::DialectType::Databricks)
8444                        | Some(crate::dialects::DialectType::Athena)
8445                );
8446                let is_doris_starrocks = matches!(
8447                    self.config.dialect,
8448                    Some(crate::dialects::DialectType::Doris)
8449                        | Some(crate::dialects::DialectType::StarRocks)
8450                );
8451                if is_hive_dialect {
8452                    self.generate_tblproperties_clause(&props.expressions)?;
8453                } else if is_doris_starrocks {
8454                    self.generate_properties_clause(&props.expressions)?;
8455                } else {
8456                    self.generate_options_clause(&props.expressions)?;
8457                }
8458            } else {
8459                self.generate_expression(prop)?;
8460            }
8461        }
8462
8463        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
8464        for prop in &ct.post_table_properties {
8465            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
8466                self.write(" WITH(");
8467                self.generate_system_versioning_content(svp)?;
8468                self.write(")");
8469            } else if let Expression::Properties(props) = prop {
8470                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
8471                let is_doris_starrocks = matches!(
8472                    self.config.dialect,
8473                    Some(crate::dialects::DialectType::Doris)
8474                        | Some(crate::dialects::DialectType::StarRocks)
8475                );
8476                self.write_space();
8477                if is_doris_starrocks {
8478                    self.generate_properties_clause(&props.expressions)?;
8479                } else {
8480                    self.generate_options_clause(&props.expressions)?;
8481                }
8482            } else {
8483                self.write_space();
8484                self.generate_expression(prop)?;
8485            }
8486        }
8487
8488        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
8489        // Only output for StarRocks target
8490        if let Some(ref rollup) = ct.rollup {
8491            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
8492                self.write_space();
8493                self.generate_rollup_property(rollup)?;
8494            }
8495        }
8496
8497        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
8498        // Only output for MySQL-compatible dialects; strip for others during transpilation
8499        // COMMENT is also used by Hive/Spark so we selectively preserve it
8500        let is_mysql_compatible = matches!(
8501            self.config.dialect,
8502            Some(DialectType::MySQL)
8503                | Some(DialectType::SingleStore)
8504                | Some(DialectType::Doris)
8505                | Some(DialectType::StarRocks)
8506                | None
8507        );
8508        let is_hive_compatible = matches!(
8509            self.config.dialect,
8510            Some(DialectType::Hive)
8511                | Some(DialectType::Spark)
8512                | Some(DialectType::Databricks)
8513                | Some(DialectType::Athena)
8514        );
8515        let mysql_pretty_options =
8516            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
8517        for (key, value) in &ct.mysql_table_options {
8518            // Skip non-MySQL-specific options for non-MySQL targets
8519            let should_output = if is_mysql_compatible {
8520                true
8521            } else if is_hive_compatible && key == "COMMENT" {
8522                true // COMMENT is valid in Hive/Spark table definitions
8523            } else {
8524                false
8525            };
8526            if should_output {
8527                if mysql_pretty_options {
8528                    self.write_newline();
8529                    self.write_indent();
8530                } else {
8531                    self.write_space();
8532                }
8533                self.write_keyword(key);
8534                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
8535                if key == "COMMENT" && !self.config.schema_comment_with_eq {
8536                    self.write_space();
8537                } else {
8538                    self.write("=");
8539                }
8540                self.write(value);
8541            }
8542        }
8543
8544        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
8545        if ct.temporary
8546            && matches!(
8547                self.config.dialect,
8548                Some(DialectType::Spark) | Some(DialectType::Databricks)
8549            )
8550            && ct.as_select.is_none()
8551        {
8552            self.write_space();
8553            self.write_keyword("USING PARQUET");
8554        }
8555
8556        // PostgreSQL INHERITS clause
8557        if !ct.inherits.is_empty() {
8558            self.write_space();
8559            self.write_keyword("INHERITS");
8560            self.write(" (");
8561            for (i, parent) in ct.inherits.iter().enumerate() {
8562                if i > 0 {
8563                    self.write(", ");
8564                }
8565                self.generate_table(parent)?;
8566            }
8567            self.write(")");
8568        }
8569
8570        // CREATE TABLE AS SELECT
8571        if let Some(ref query) = ct.as_select {
8572            self.write_space();
8573            self.write_keyword("AS");
8574            self.write_space();
8575            let source_is_clickhouse =
8576                matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
8577            let wrap_as_select =
8578                ct.as_select_parenthesized && !(is_clickhouse && source_is_clickhouse);
8579            if wrap_as_select {
8580                self.write("(");
8581            }
8582            self.generate_expression(query)?;
8583            if wrap_as_select {
8584                self.write(")");
8585            }
8586
8587            // Teradata: WITH DATA / WITH NO DATA
8588            if let Some(with_data) = ct.with_data {
8589                self.write_space();
8590                self.write_keyword("WITH");
8591                if !with_data {
8592                    self.write_space();
8593                    self.write_keyword("NO");
8594                }
8595                self.write_space();
8596                self.write_keyword("DATA");
8597            }
8598
8599            // Teradata: AND STATISTICS / AND NO STATISTICS
8600            if let Some(with_statistics) = ct.with_statistics {
8601                self.write_space();
8602                self.write_keyword("AND");
8603                if !with_statistics {
8604                    self.write_space();
8605                    self.write_keyword("NO");
8606                }
8607                self.write_space();
8608                self.write_keyword("STATISTICS");
8609            }
8610
8611            // Teradata: Index specifications
8612            for index in &ct.teradata_indexes {
8613                self.write_space();
8614                match index.kind {
8615                    TeradataIndexKind::NoPrimary => {
8616                        self.write_keyword("NO PRIMARY INDEX");
8617                    }
8618                    TeradataIndexKind::Primary => {
8619                        self.write_keyword("PRIMARY INDEX");
8620                    }
8621                    TeradataIndexKind::PrimaryAmp => {
8622                        self.write_keyword("PRIMARY AMP INDEX");
8623                    }
8624                    TeradataIndexKind::Unique => {
8625                        self.write_keyword("UNIQUE INDEX");
8626                    }
8627                    TeradataIndexKind::UniquePrimary => {
8628                        self.write_keyword("UNIQUE PRIMARY INDEX");
8629                    }
8630                    TeradataIndexKind::Secondary => {
8631                        self.write_keyword("INDEX");
8632                    }
8633                }
8634                // Output index name if present
8635                if let Some(ref name) = index.name {
8636                    self.write_space();
8637                    self.write(name);
8638                }
8639                // Output columns if present
8640                if !index.columns.is_empty() {
8641                    self.write(" (");
8642                    for (i, col) in index.columns.iter().enumerate() {
8643                        if i > 0 {
8644                            self.write(", ");
8645                        }
8646                        self.write(col);
8647                    }
8648                    self.write(")");
8649                }
8650            }
8651
8652            // Teradata: ON COMMIT behavior for volatile tables
8653            if let Some(ref on_commit) = ct.on_commit {
8654                self.write_space();
8655                self.write_keyword("ON COMMIT");
8656                self.write_space();
8657                match on_commit {
8658                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8659                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8660                }
8661            }
8662
8663            if !post_as_properties.is_empty() {
8664                for prop in post_as_properties {
8665                    self.write_space();
8666                    self.generate_expression(prop)?;
8667                }
8668            }
8669
8670            // Restore Athena Hive context before early return
8671            self.athena_hive_context = saved_athena_hive_context;
8672            return Ok(());
8673        }
8674
8675        // ON COMMIT behavior (for non-CTAS tables)
8676        if let Some(ref on_commit) = ct.on_commit {
8677            self.write_space();
8678            self.write_keyword("ON COMMIT");
8679            self.write_space();
8680            match on_commit {
8681                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8682                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8683            }
8684        }
8685
8686        // Restore Athena Hive context
8687        self.athena_hive_context = saved_athena_hive_context;
8688
8689        Ok(())
8690    }
8691
8692    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
8693    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
8694    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
8695        // Output column name
8696        self.generate_identifier(&col.name)?;
8697        // Output data type if known
8698        if !matches!(col.data_type, DataType::Unknown) {
8699            self.write_space();
8700            self.generate_data_type(&col.data_type)?;
8701        }
8702        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
8703        for constraint in &col.constraints {
8704            if let ColumnConstraint::Path(path_expr) = constraint {
8705                self.write_space();
8706                self.write_keyword("PATH");
8707                self.write_space();
8708                self.generate_expression(path_expr)?;
8709            }
8710        }
8711        Ok(())
8712    }
8713
8714    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
8715        // Check if this is a TSQL computed column (no data type)
8716        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8717            && col
8718                .constraints
8719                .iter()
8720                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8721        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
8722        let omit_computed_type = !self.config.computed_column_with_type
8723            && col
8724                .constraints
8725                .iter()
8726                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8727
8728        // Check if this is a partition column spec (no data type, type is Unknown)
8729        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
8730        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
8731
8732        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
8733        // Also check the no_type flag for SQLite columns without types
8734        let has_no_type = col.no_type
8735            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8736                && col.constraints.is_empty());
8737
8738        self.generate_identifier(&col.name)?;
8739
8740        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
8741        let serial_expansion = if matches!(
8742            self.config.dialect,
8743            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
8744        ) {
8745            if let DataType::Custom { ref name } = col.data_type {
8746                if name.eq_ignore_ascii_case("SERIAL") {
8747                    Some("INT")
8748                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
8749                    Some("BIGINT")
8750                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
8751                    Some("SMALLINT")
8752                } else {
8753                    None
8754                }
8755            } else {
8756                None
8757            }
8758        } else {
8759            None
8760        };
8761
8762        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
8763        {
8764            self.write_space();
8765            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
8766            // since ClickHouse uses explicit Nullable() in its type system.
8767            let saved_nullable_depth = self.clickhouse_nullable_depth;
8768            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
8769                self.clickhouse_nullable_depth = -1;
8770            }
8771            if let Some(int_type) = serial_expansion {
8772                // SERIAL -> INT (+ constraints added below)
8773                self.write_keyword(int_type);
8774            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8775                // For DuckDB: convert unsigned integer types to their unsigned equivalents
8776                let unsigned_type = match &col.data_type {
8777                    DataType::Int { .. } => Some("UINTEGER"),
8778                    DataType::BigInt { .. } => Some("UBIGINT"),
8779                    DataType::SmallInt { .. } => Some("USMALLINT"),
8780                    DataType::TinyInt { .. } => Some("UTINYINT"),
8781                    _ => None,
8782                };
8783                if let Some(utype) = unsigned_type {
8784                    self.write_keyword(utype);
8785                } else {
8786                    self.generate_data_type(&col.data_type)?;
8787                }
8788            } else {
8789                self.generate_data_type(&col.data_type)?;
8790            }
8791            self.clickhouse_nullable_depth = saved_nullable_depth;
8792        }
8793
8794        // MySQL type modifiers (must come right after data type)
8795        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
8796        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8797            self.write_space();
8798            self.write_keyword("UNSIGNED");
8799        }
8800        if col.zerofill {
8801            self.write_space();
8802            self.write_keyword("ZEROFILL");
8803        }
8804
8805        // Teradata column attributes (must come right after data type, in specific order)
8806        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
8807
8808        if let Some(ref charset) = col.character_set {
8809            self.write_space();
8810            self.write_keyword("CHARACTER SET");
8811            self.write_space();
8812            self.write(charset);
8813        }
8814
8815        if col.uppercase {
8816            self.write_space();
8817            self.write_keyword("UPPERCASE");
8818        }
8819
8820        if let Some(casespecific) = col.casespecific {
8821            self.write_space();
8822            if casespecific {
8823                self.write_keyword("CASESPECIFIC");
8824            } else {
8825                self.write_keyword("NOT CASESPECIFIC");
8826            }
8827        }
8828
8829        if let Some(ref format) = col.format {
8830            self.write_space();
8831            self.write_keyword("FORMAT");
8832            self.write(" '");
8833            self.write(format);
8834            self.write("'");
8835        }
8836
8837        if let Some(ref title) = col.title {
8838            self.write_space();
8839            self.write_keyword("TITLE");
8840            self.write(" '");
8841            self.write(title);
8842            self.write("'");
8843        }
8844
8845        if let Some(length) = col.inline_length {
8846            self.write_space();
8847            self.write_keyword("INLINE LENGTH");
8848            self.write(" ");
8849            self.write(&length.to_string());
8850        }
8851
8852        if let Some(ref compress) = col.compress {
8853            self.write_space();
8854            self.write_keyword("COMPRESS");
8855            if !compress.is_empty() {
8856                // Single string literal: output without parentheses (Teradata syntax)
8857                if compress.len() == 1 {
8858                    if let Expression::Literal(lit) = &compress[0] {
8859                        if let Literal::String(_) = lit.as_ref() {
8860                            self.write_space();
8861                            self.generate_expression(&compress[0])?;
8862                        }
8863                    } else {
8864                        self.write(" (");
8865                        self.generate_expression(&compress[0])?;
8866                        self.write(")");
8867                    }
8868                } else {
8869                    self.write(" (");
8870                    for (i, val) in compress.iter().enumerate() {
8871                        if i > 0 {
8872                            self.write(", ");
8873                        }
8874                        self.generate_expression(val)?;
8875                    }
8876                    self.write(")");
8877                }
8878            }
8879        }
8880
8881        // Column constraints - output in original order if constraint_order is populated
8882        // Otherwise fall back to legacy fixed order for backward compatibility
8883        if !col.constraint_order.is_empty() {
8884            // Use constraint_order for original ordering
8885            // Track indices for constraints stored in the constraints Vec
8886            let mut references_idx = 0;
8887            let mut check_idx = 0;
8888            let mut generated_idx = 0;
8889            let mut collate_idx = 0;
8890            let mut comment_idx = 0;
8891            // The preprocessing in dialects/mod.rs now handles the correct ordering of
8892            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
8893            let defer_not_null_after_identity = false;
8894            let mut pending_not_null_after_identity = false;
8895
8896            for constraint_type in &col.constraint_order {
8897                match constraint_type {
8898                    ConstraintType::PrimaryKey => {
8899                        // Materialize doesn't support PRIMARY KEY column constraints
8900                        if col.primary_key
8901                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
8902                        {
8903                            if let Some(ref cname) = col.primary_key_constraint_name {
8904                                self.write_space();
8905                                self.write_keyword("CONSTRAINT");
8906                                self.write_space();
8907                                self.write(cname);
8908                            }
8909                            self.write_space();
8910                            self.write_keyword("PRIMARY KEY");
8911                            if let Some(ref order) = col.primary_key_order {
8912                                self.write_space();
8913                                match order {
8914                                    SortOrder::Asc => self.write_keyword("ASC"),
8915                                    SortOrder::Desc => self.write_keyword("DESC"),
8916                                }
8917                            }
8918                        }
8919                    }
8920                    ConstraintType::Unique => {
8921                        if col.unique {
8922                            if let Some(ref cname) = col.unique_constraint_name {
8923                                self.write_space();
8924                                self.write_keyword("CONSTRAINT");
8925                                self.write_space();
8926                                self.write(cname);
8927                            }
8928                            self.write_space();
8929                            self.write_keyword("UNIQUE");
8930                            // PostgreSQL 15+: NULLS NOT DISTINCT
8931                            if col.unique_nulls_not_distinct {
8932                                self.write(" NULLS NOT DISTINCT");
8933                            }
8934                        }
8935                    }
8936                    ConstraintType::NotNull => {
8937                        if col.nullable == Some(false) {
8938                            if defer_not_null_after_identity {
8939                                pending_not_null_after_identity = true;
8940                                continue;
8941                            }
8942                            if let Some(ref cname) = col.not_null_constraint_name {
8943                                self.write_space();
8944                                self.write_keyword("CONSTRAINT");
8945                                self.write_space();
8946                                self.write(cname);
8947                            }
8948                            self.write_space();
8949                            self.write_keyword("NOT NULL");
8950                        }
8951                    }
8952                    ConstraintType::Null => {
8953                        if col.nullable == Some(true) {
8954                            self.write_space();
8955                            self.write_keyword("NULL");
8956                        }
8957                    }
8958                    ConstraintType::Default => {
8959                        if let Some(ref default) = col.default {
8960                            self.write_space();
8961                            self.write_keyword("DEFAULT");
8962                            self.write_space();
8963                            self.generate_expression(default)?;
8964                        }
8965                    }
8966                    ConstraintType::AutoIncrement => {
8967                        if col.auto_increment {
8968                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8969                            if matches!(
8970                                self.config.dialect,
8971                                Some(crate::dialects::DialectType::DuckDB)
8972                            ) {
8973                                // Skip - DuckDB uses sequences or rowid instead
8974                            } else if matches!(
8975                                self.config.dialect,
8976                                Some(crate::dialects::DialectType::Materialize)
8977                            ) {
8978                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8979                                if !matches!(col.nullable, Some(false)) {
8980                                    self.write_space();
8981                                    self.write_keyword("NOT NULL");
8982                                }
8983                            } else if matches!(
8984                                self.config.dialect,
8985                                Some(crate::dialects::DialectType::PostgreSQL)
8986                            ) {
8987                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8988                                self.write_space();
8989                                self.generate_auto_increment_keyword(col)?;
8990                            } else if matches!(
8991                                self.config.dialect,
8992                                Some(crate::dialects::DialectType::SQLite)
8993                            ) && !col.primary_key
8994                                && self
8995                                    .sqlite_inline_pk_columns
8996                                    .contains(&col.name.name.to_ascii_lowercase())
8997                            {
8998                                // SQLite requires AUTOINCREMENT after PRIMARY KEY.
8999                                // The table-level primary key is emitted later inline.
9000                            } else {
9001                                self.write_space();
9002                                self.generate_auto_increment_keyword(col)?;
9003                                if pending_not_null_after_identity {
9004                                    self.write_space();
9005                                    self.write_keyword("NOT NULL");
9006                                    pending_not_null_after_identity = false;
9007                                }
9008                            }
9009                        } // close else for DuckDB skip
9010                    }
9011                    ConstraintType::References => {
9012                        // Find next References constraint
9013                        while references_idx < col.constraints.len() {
9014                            if let ColumnConstraint::References(fk_ref) =
9015                                &col.constraints[references_idx]
9016                            {
9017                                // CONSTRAINT name if present
9018                                if let Some(ref name) = fk_ref.constraint_name {
9019                                    self.write_space();
9020                                    self.write_keyword("CONSTRAINT");
9021                                    self.write_space();
9022                                    self.write(name);
9023                                }
9024                                self.write_space();
9025                                if fk_ref.has_foreign_key_keywords {
9026                                    self.write_keyword("FOREIGN KEY");
9027                                    self.write_space();
9028                                }
9029                                self.write_keyword("REFERENCES");
9030                                self.write_space();
9031                                self.generate_table(&fk_ref.table)?;
9032                                if !fk_ref.columns.is_empty() {
9033                                    self.write(" (");
9034                                    for (i, c) in fk_ref.columns.iter().enumerate() {
9035                                        if i > 0 {
9036                                            self.write(", ");
9037                                        }
9038                                        self.generate_identifier(c)?;
9039                                    }
9040                                    self.write(")");
9041                                }
9042                                self.generate_referential_actions(fk_ref)?;
9043                                references_idx += 1;
9044                                break;
9045                            }
9046                            references_idx += 1;
9047                        }
9048                    }
9049                    ConstraintType::Check => {
9050                        // Find next Check constraint
9051                        while check_idx < col.constraints.len() {
9052                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
9053                                // Output CONSTRAINT name if present (only for first CHECK)
9054                                if check_idx == 0 {
9055                                    if let Some(ref cname) = col.check_constraint_name {
9056                                        self.write_space();
9057                                        self.write_keyword("CONSTRAINT");
9058                                        self.write_space();
9059                                        self.write(cname);
9060                                    }
9061                                }
9062                                self.write_space();
9063                                self.write_keyword("CHECK");
9064                                self.write(" (");
9065                                self.generate_expression(expr)?;
9066                                self.write(")");
9067                                check_idx += 1;
9068                                break;
9069                            }
9070                            check_idx += 1;
9071                        }
9072                    }
9073                    ConstraintType::GeneratedAsIdentity => {
9074                        // Find next GeneratedAsIdentity constraint
9075                        while generated_idx < col.constraints.len() {
9076                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
9077                                &col.constraints[generated_idx]
9078                            {
9079                                self.write_space();
9080                                // Redshift uses IDENTITY(start, increment) syntax
9081                                if matches!(
9082                                    self.config.dialect,
9083                                    Some(crate::dialects::DialectType::Redshift)
9084                                ) {
9085                                    self.write_keyword("IDENTITY");
9086                                    self.write("(");
9087                                    if let Some(ref start) = gen.start {
9088                                        self.generate_expression(start)?;
9089                                    } else {
9090                                        self.write("0");
9091                                    }
9092                                    self.write(", ");
9093                                    if let Some(ref incr) = gen.increment {
9094                                        self.generate_expression(incr)?;
9095                                    } else {
9096                                        self.write("1");
9097                                    }
9098                                    self.write(")");
9099                                } else {
9100                                    self.write_keyword("GENERATED");
9101                                    if gen.always {
9102                                        self.write_space();
9103                                        self.write_keyword("ALWAYS");
9104                                    } else {
9105                                        self.write_space();
9106                                        self.write_keyword("BY DEFAULT");
9107                                        if gen.on_null {
9108                                            self.write_space();
9109                                            self.write_keyword("ON NULL");
9110                                        }
9111                                    }
9112                                    self.write_space();
9113                                    self.write_keyword("AS IDENTITY");
9114
9115                                    let has_options = gen.start.is_some()
9116                                        || gen.increment.is_some()
9117                                        || gen.minvalue.is_some()
9118                                        || gen.maxvalue.is_some()
9119                                        || gen.cycle.is_some();
9120                                    if has_options {
9121                                        self.write(" (");
9122                                        let mut first = true;
9123                                        if let Some(ref start) = gen.start {
9124                                            if !first {
9125                                                self.write(" ");
9126                                            }
9127                                            first = false;
9128                                            self.write_keyword("START WITH");
9129                                            self.write_space();
9130                                            self.generate_expression(start)?;
9131                                        }
9132                                        if let Some(ref incr) = gen.increment {
9133                                            if !first {
9134                                                self.write(" ");
9135                                            }
9136                                            first = false;
9137                                            self.write_keyword("INCREMENT BY");
9138                                            self.write_space();
9139                                            self.generate_expression(incr)?;
9140                                        }
9141                                        if let Some(ref minv) = gen.minvalue {
9142                                            if !first {
9143                                                self.write(" ");
9144                                            }
9145                                            first = false;
9146                                            self.write_keyword("MINVALUE");
9147                                            self.write_space();
9148                                            self.generate_expression(minv)?;
9149                                        }
9150                                        if let Some(ref maxv) = gen.maxvalue {
9151                                            if !first {
9152                                                self.write(" ");
9153                                            }
9154                                            first = false;
9155                                            self.write_keyword("MAXVALUE");
9156                                            self.write_space();
9157                                            self.generate_expression(maxv)?;
9158                                        }
9159                                        if let Some(cycle) = gen.cycle {
9160                                            if !first {
9161                                                self.write(" ");
9162                                            }
9163                                            if cycle {
9164                                                self.write_keyword("CYCLE");
9165                                            } else {
9166                                                self.write_keyword("NO CYCLE");
9167                                            }
9168                                        }
9169                                        self.write(")");
9170                                    }
9171                                }
9172                                generated_idx += 1;
9173                                break;
9174                            }
9175                            generated_idx += 1;
9176                        }
9177                    }
9178                    ConstraintType::Collate => {
9179                        // Find next Collate constraint
9180                        while collate_idx < col.constraints.len() {
9181                            if let ColumnConstraint::Collate(collation) =
9182                                &col.constraints[collate_idx]
9183                            {
9184                                self.write_space();
9185                                self.write_keyword("COLLATE");
9186                                self.write_space();
9187                                self.generate_identifier(collation)?;
9188                                collate_idx += 1;
9189                                break;
9190                            }
9191                            collate_idx += 1;
9192                        }
9193                    }
9194                    ConstraintType::Comment => {
9195                        // Find next Comment constraint
9196                        while comment_idx < col.constraints.len() {
9197                            if let ColumnConstraint::Comment(comment) =
9198                                &col.constraints[comment_idx]
9199                            {
9200                                self.write_space();
9201                                self.write_keyword("COMMENT");
9202                                self.write_space();
9203                                self.generate_string_literal(comment)?;
9204                                comment_idx += 1;
9205                                break;
9206                            }
9207                            comment_idx += 1;
9208                        }
9209                    }
9210                    ConstraintType::Tags => {
9211                        // Find next Tags constraint (Snowflake)
9212                        for constraint in &col.constraints {
9213                            if let ColumnConstraint::Tags(tags) = constraint {
9214                                self.write_space();
9215                                self.write_keyword("TAG");
9216                                self.write(" (");
9217                                for (i, expr) in tags.expressions.iter().enumerate() {
9218                                    if i > 0 {
9219                                        self.write(", ");
9220                                    }
9221                                    self.generate_expression(expr)?;
9222                                }
9223                                self.write(")");
9224                                break;
9225                            }
9226                        }
9227                    }
9228                    ConstraintType::ComputedColumn => {
9229                        // Find next ComputedColumn constraint
9230                        for constraint in &col.constraints {
9231                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
9232                                self.write_space();
9233                                self.generate_computed_column_inline(cc)?;
9234                                break;
9235                            }
9236                        }
9237                    }
9238                    ConstraintType::GeneratedAsRow => {
9239                        // Find next GeneratedAsRow constraint
9240                        for constraint in &col.constraints {
9241                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
9242                                self.write_space();
9243                                self.generate_generated_as_row_inline(gar)?;
9244                                break;
9245                            }
9246                        }
9247                    }
9248                    ConstraintType::OnUpdate => {
9249                        if let Some(ref expr) = col.on_update {
9250                            self.write_space();
9251                            self.write_keyword("ON UPDATE");
9252                            self.write_space();
9253                            self.generate_expression(expr)?;
9254                        }
9255                    }
9256                    ConstraintType::Encode => {
9257                        if let Some(ref encoding) = col.encoding {
9258                            self.write_space();
9259                            self.write_keyword("ENCODE");
9260                            self.write_space();
9261                            self.write(encoding);
9262                        }
9263                    }
9264                    ConstraintType::Path => {
9265                        // Find next Path constraint
9266                        for constraint in &col.constraints {
9267                            if let ColumnConstraint::Path(path_expr) = constraint {
9268                                self.write_space();
9269                                self.write_keyword("PATH");
9270                                self.write_space();
9271                                self.generate_expression(path_expr)?;
9272                                break;
9273                            }
9274                        }
9275                    }
9276                }
9277            }
9278            if pending_not_null_after_identity {
9279                self.write_space();
9280                self.write_keyword("NOT NULL");
9281            }
9282        } else {
9283            // Legacy fixed order for backward compatibility
9284            if col.primary_key {
9285                self.write_space();
9286                self.write_keyword("PRIMARY KEY");
9287                if let Some(ref order) = col.primary_key_order {
9288                    self.write_space();
9289                    match order {
9290                        SortOrder::Asc => self.write_keyword("ASC"),
9291                        SortOrder::Desc => self.write_keyword("DESC"),
9292                    }
9293                }
9294            }
9295
9296            if col.unique {
9297                self.write_space();
9298                self.write_keyword("UNIQUE");
9299                // PostgreSQL 15+: NULLS NOT DISTINCT
9300                if col.unique_nulls_not_distinct {
9301                    self.write(" NULLS NOT DISTINCT");
9302                }
9303            }
9304
9305            match col.nullable {
9306                Some(false) => {
9307                    self.write_space();
9308                    self.write_keyword("NOT NULL");
9309                }
9310                Some(true) => {
9311                    self.write_space();
9312                    self.write_keyword("NULL");
9313                }
9314                None => {}
9315            }
9316
9317            if let Some(ref default) = col.default {
9318                self.write_space();
9319                self.write_keyword("DEFAULT");
9320                self.write_space();
9321                self.generate_expression(default)?;
9322            }
9323
9324            if col.auto_increment {
9325                self.write_space();
9326                self.generate_auto_increment_keyword(col)?;
9327            }
9328
9329            // Column-level constraints from Vec
9330            for constraint in &col.constraints {
9331                match constraint {
9332                    ColumnConstraint::References(fk_ref) => {
9333                        self.write_space();
9334                        if fk_ref.has_foreign_key_keywords {
9335                            self.write_keyword("FOREIGN KEY");
9336                            self.write_space();
9337                        }
9338                        self.write_keyword("REFERENCES");
9339                        self.write_space();
9340                        self.generate_table(&fk_ref.table)?;
9341                        if !fk_ref.columns.is_empty() {
9342                            self.write(" (");
9343                            for (i, c) in fk_ref.columns.iter().enumerate() {
9344                                if i > 0 {
9345                                    self.write(", ");
9346                                }
9347                                self.generate_identifier(c)?;
9348                            }
9349                            self.write(")");
9350                        }
9351                        self.generate_referential_actions(fk_ref)?;
9352                    }
9353                    ColumnConstraint::Check(expr) => {
9354                        self.write_space();
9355                        self.write_keyword("CHECK");
9356                        self.write(" (");
9357                        self.generate_expression(expr)?;
9358                        self.write(")");
9359                    }
9360                    ColumnConstraint::GeneratedAsIdentity(gen) => {
9361                        self.write_space();
9362                        // Redshift uses IDENTITY(start, increment) syntax
9363                        if matches!(
9364                            self.config.dialect,
9365                            Some(crate::dialects::DialectType::Redshift)
9366                        ) {
9367                            self.write_keyword("IDENTITY");
9368                            self.write("(");
9369                            if let Some(ref start) = gen.start {
9370                                self.generate_expression(start)?;
9371                            } else {
9372                                self.write("0");
9373                            }
9374                            self.write(", ");
9375                            if let Some(ref incr) = gen.increment {
9376                                self.generate_expression(incr)?;
9377                            } else {
9378                                self.write("1");
9379                            }
9380                            self.write(")");
9381                        } else {
9382                            self.write_keyword("GENERATED");
9383                            if gen.always {
9384                                self.write_space();
9385                                self.write_keyword("ALWAYS");
9386                            } else {
9387                                self.write_space();
9388                                self.write_keyword("BY DEFAULT");
9389                                if gen.on_null {
9390                                    self.write_space();
9391                                    self.write_keyword("ON NULL");
9392                                }
9393                            }
9394                            self.write_space();
9395                            self.write_keyword("AS IDENTITY");
9396
9397                            let has_options = gen.start.is_some()
9398                                || gen.increment.is_some()
9399                                || gen.minvalue.is_some()
9400                                || gen.maxvalue.is_some()
9401                                || gen.cycle.is_some();
9402                            if has_options {
9403                                self.write(" (");
9404                                let mut first = true;
9405                                if let Some(ref start) = gen.start {
9406                                    if !first {
9407                                        self.write(" ");
9408                                    }
9409                                    first = false;
9410                                    self.write_keyword("START WITH");
9411                                    self.write_space();
9412                                    self.generate_expression(start)?;
9413                                }
9414                                if let Some(ref incr) = gen.increment {
9415                                    if !first {
9416                                        self.write(" ");
9417                                    }
9418                                    first = false;
9419                                    self.write_keyword("INCREMENT BY");
9420                                    self.write_space();
9421                                    self.generate_expression(incr)?;
9422                                }
9423                                if let Some(ref minv) = gen.minvalue {
9424                                    if !first {
9425                                        self.write(" ");
9426                                    }
9427                                    first = false;
9428                                    self.write_keyword("MINVALUE");
9429                                    self.write_space();
9430                                    self.generate_expression(minv)?;
9431                                }
9432                                if let Some(ref maxv) = gen.maxvalue {
9433                                    if !first {
9434                                        self.write(" ");
9435                                    }
9436                                    first = false;
9437                                    self.write_keyword("MAXVALUE");
9438                                    self.write_space();
9439                                    self.generate_expression(maxv)?;
9440                                }
9441                                if let Some(cycle) = gen.cycle {
9442                                    if !first {
9443                                        self.write(" ");
9444                                    }
9445                                    if cycle {
9446                                        self.write_keyword("CYCLE");
9447                                    } else {
9448                                        self.write_keyword("NO CYCLE");
9449                                    }
9450                                }
9451                                self.write(")");
9452                            }
9453                        }
9454                    }
9455                    ColumnConstraint::Collate(collation) => {
9456                        self.write_space();
9457                        self.write_keyword("COLLATE");
9458                        self.write_space();
9459                        self.generate_identifier(collation)?;
9460                    }
9461                    ColumnConstraint::Comment(comment) => {
9462                        self.write_space();
9463                        self.write_keyword("COMMENT");
9464                        self.write_space();
9465                        self.generate_string_literal(comment)?;
9466                    }
9467                    ColumnConstraint::Path(path_expr) => {
9468                        self.write_space();
9469                        self.write_keyword("PATH");
9470                        self.write_space();
9471                        self.generate_expression(path_expr)?;
9472                    }
9473                    _ => {} // Other constraints handled above
9474                }
9475            }
9476
9477            // Redshift: ENCODE encoding_type (legacy path)
9478            if let Some(ref encoding) = col.encoding {
9479                self.write_space();
9480                self.write_keyword("ENCODE");
9481                self.write_space();
9482                self.write(encoding);
9483            }
9484        }
9485
9486        // ClickHouse: CODEC(...)
9487        if let Some(ref codec) = col.codec {
9488            self.write_space();
9489            self.write_keyword("CODEC");
9490            self.write("(");
9491            self.write(codec);
9492            self.write(")");
9493        }
9494
9495        if let Some(visible) = col.visible {
9496            self.write_space();
9497            if visible {
9498                self.write_keyword("VISIBLE");
9499            } else {
9500                self.write_keyword("INVISIBLE");
9501            }
9502        }
9503
9504        // ClickHouse: EPHEMERAL [expr]
9505        if let Some(ref ephemeral) = col.ephemeral {
9506            self.write_space();
9507            self.write_keyword("EPHEMERAL");
9508            if let Some(ref expr) = ephemeral {
9509                self.write_space();
9510                self.generate_expression(expr)?;
9511            }
9512        }
9513
9514        // ClickHouse: MATERIALIZED expr
9515        if let Some(ref mat_expr) = col.materialized_expr {
9516            self.write_space();
9517            self.write_keyword("MATERIALIZED");
9518            self.write_space();
9519            self.generate_expression(mat_expr)?;
9520        }
9521
9522        // ClickHouse: ALIAS expr
9523        if let Some(ref alias_expr) = col.alias_expr {
9524            self.write_space();
9525            self.write_keyword("ALIAS");
9526            self.write_space();
9527            self.generate_expression(alias_expr)?;
9528        }
9529
9530        // ClickHouse: TTL expr
9531        if let Some(ref ttl_expr) = col.ttl_expr {
9532            self.write_space();
9533            self.write_keyword("TTL");
9534            self.write_space();
9535            self.generate_expression(ttl_expr)?;
9536        }
9537
9538        // TSQL: NOT FOR REPLICATION
9539        if col.not_for_replication
9540            && matches!(
9541                self.config.dialect,
9542                Some(crate::dialects::DialectType::TSQL)
9543                    | Some(crate::dialects::DialectType::Fabric)
9544            )
9545        {
9546            self.write_space();
9547            self.write_keyword("NOT FOR REPLICATION");
9548        }
9549
9550        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
9551        if !col.options.is_empty() {
9552            self.write_space();
9553            self.generate_options_clause(&col.options)?;
9554        }
9555
9556        // SQLite: Inline PRIMARY KEY from table constraint
9557        // This comes at the end, after all existing column constraints
9558        if !col.primary_key
9559            && self
9560                .sqlite_inline_pk_columns
9561                .contains(&col.name.name.to_ascii_lowercase())
9562        {
9563            self.write_space();
9564            self.write_keyword("PRIMARY KEY");
9565            if matches!(self.config.dialect, Some(DialectType::SQLite)) && col.auto_increment {
9566                self.write_space();
9567                self.generate_auto_increment_keyword(col)?;
9568            }
9569        }
9570
9571        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
9572        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
9573        if serial_expansion.is_some() {
9574            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
9575                self.write_space();
9576                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
9577            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
9578                self.write_space();
9579                self.write_keyword("NOT NULL");
9580            }
9581        }
9582
9583        Ok(())
9584    }
9585
9586    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
9587        match constraint {
9588            TableConstraint::PrimaryKey {
9589                name,
9590                columns,
9591                include_columns,
9592                modifiers,
9593                has_constraint_keyword,
9594            } => {
9595                if let Some(ref n) = name {
9596                    if *has_constraint_keyword {
9597                        self.write_keyword("CONSTRAINT");
9598                        self.write_space();
9599                        self.generate_identifier(n)?;
9600                        self.write_space();
9601                    }
9602                }
9603                self.write_keyword("PRIMARY KEY");
9604                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9605                if let Some(ref clustered) = modifiers.clustered {
9606                    self.write_space();
9607                    self.write_keyword(clustered);
9608                }
9609                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
9610                if let Some(ref n) = name {
9611                    if !*has_constraint_keyword {
9612                        self.write_space();
9613                        self.generate_identifier(n)?;
9614                    }
9615                }
9616                self.write(" (");
9617                for (i, col) in columns.iter().enumerate() {
9618                    if i > 0 {
9619                        self.write(", ");
9620                    }
9621                    self.generate_identifier(col)?;
9622                }
9623                self.write(")");
9624                if !include_columns.is_empty() {
9625                    self.write_space();
9626                    self.write_keyword("INCLUDE");
9627                    self.write(" (");
9628                    for (i, col) in include_columns.iter().enumerate() {
9629                        if i > 0 {
9630                            self.write(", ");
9631                        }
9632                        self.generate_identifier(col)?;
9633                    }
9634                    self.write(")");
9635                }
9636                self.generate_constraint_modifiers(modifiers);
9637            }
9638            TableConstraint::Unique {
9639                name,
9640                columns,
9641                columns_parenthesized,
9642                modifiers,
9643                has_constraint_keyword,
9644                nulls_not_distinct,
9645            } => {
9646                if let Some(ref n) = name {
9647                    if *has_constraint_keyword {
9648                        self.write_keyword("CONSTRAINT");
9649                        self.write_space();
9650                        self.generate_identifier(n)?;
9651                        self.write_space();
9652                    }
9653                }
9654                self.write_keyword("UNIQUE");
9655                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9656                if let Some(ref clustered) = modifiers.clustered {
9657                    self.write_space();
9658                    self.write_keyword(clustered);
9659                }
9660                // PostgreSQL 15+: NULLS NOT DISTINCT
9661                if *nulls_not_distinct {
9662                    self.write(" NULLS NOT DISTINCT");
9663                }
9664                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
9665                if let Some(ref n) = name {
9666                    if !*has_constraint_keyword {
9667                        self.write_space();
9668                        self.generate_identifier(n)?;
9669                    }
9670                }
9671                if *columns_parenthesized {
9672                    self.write(" (");
9673                    for (i, col) in columns.iter().enumerate() {
9674                        if i > 0 {
9675                            self.write(", ");
9676                        }
9677                        self.generate_identifier(col)?;
9678                    }
9679                    self.write(")");
9680                } else {
9681                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
9682                    for col in columns.iter() {
9683                        self.write_space();
9684                        self.generate_identifier(col)?;
9685                    }
9686                }
9687                self.generate_constraint_modifiers(modifiers);
9688            }
9689            TableConstraint::ForeignKey {
9690                name,
9691                columns,
9692                references,
9693                on_delete,
9694                on_update,
9695                modifiers,
9696            } => {
9697                if let Some(ref n) = name {
9698                    self.write_keyword("CONSTRAINT");
9699                    self.write_space();
9700                    self.generate_identifier(n)?;
9701                    self.write_space();
9702                }
9703                self.write_keyword("FOREIGN KEY");
9704                self.write(" (");
9705                for (i, col) in columns.iter().enumerate() {
9706                    if i > 0 {
9707                        self.write(", ");
9708                    }
9709                    self.generate_identifier(col)?;
9710                }
9711                self.write(")");
9712                if let Some(ref refs) = references {
9713                    self.write(" ");
9714                    self.write_keyword("REFERENCES");
9715                    self.write_space();
9716                    self.generate_table(&refs.table)?;
9717                    if !refs.columns.is_empty() {
9718                        if self.config.pretty {
9719                            self.write(" (");
9720                            self.write_newline();
9721                            self.indent_level += 1;
9722                            for (i, col) in refs.columns.iter().enumerate() {
9723                                if i > 0 {
9724                                    self.write(",");
9725                                    self.write_newline();
9726                                }
9727                                self.write_indent();
9728                                self.generate_identifier(col)?;
9729                            }
9730                            self.indent_level -= 1;
9731                            self.write_newline();
9732                            self.write_indent();
9733                            self.write(")");
9734                        } else {
9735                            self.write(" (");
9736                            for (i, col) in refs.columns.iter().enumerate() {
9737                                if i > 0 {
9738                                    self.write(", ");
9739                                }
9740                                self.generate_identifier(col)?;
9741                            }
9742                            self.write(")");
9743                        }
9744                    }
9745                    self.generate_referential_actions(refs)?;
9746                } else {
9747                    // No REFERENCES - output ON DELETE/ON UPDATE directly
9748                    if let Some(ref action) = on_delete {
9749                        self.write_space();
9750                        self.write_keyword("ON DELETE");
9751                        self.write_space();
9752                        self.generate_referential_action(action);
9753                    }
9754                    if let Some(ref action) = on_update {
9755                        self.write_space();
9756                        self.write_keyword("ON UPDATE");
9757                        self.write_space();
9758                        self.generate_referential_action(action);
9759                    }
9760                }
9761                self.generate_constraint_modifiers(modifiers);
9762            }
9763            TableConstraint::Check {
9764                name,
9765                expression,
9766                modifiers,
9767            } => {
9768                if let Some(ref n) = name {
9769                    self.write_keyword("CONSTRAINT");
9770                    self.write_space();
9771                    self.generate_identifier(n)?;
9772                    self.write_space();
9773                }
9774                self.write_keyword("CHECK");
9775                self.write(" (");
9776                self.generate_expression(expression)?;
9777                self.write(")");
9778                self.generate_constraint_modifiers(modifiers);
9779            }
9780            TableConstraint::Assume { name, expression } => {
9781                if let Some(ref n) = name {
9782                    self.write_keyword("CONSTRAINT");
9783                    self.write_space();
9784                    self.generate_identifier(n)?;
9785                    self.write_space();
9786                }
9787                self.write_keyword("ASSUME");
9788                self.write(" (");
9789                self.generate_expression(expression)?;
9790                self.write(")");
9791            }
9792            TableConstraint::Default {
9793                name,
9794                expression,
9795                column,
9796            } => {
9797                if let Some(ref n) = name {
9798                    self.write_keyword("CONSTRAINT");
9799                    self.write_space();
9800                    self.generate_identifier(n)?;
9801                    self.write_space();
9802                }
9803                self.write_keyword("DEFAULT");
9804                self.write_space();
9805                self.generate_expression(expression)?;
9806                self.write_space();
9807                self.write_keyword("FOR");
9808                self.write_space();
9809                self.generate_identifier(column)?;
9810            }
9811            TableConstraint::Index {
9812                name,
9813                columns,
9814                kind,
9815                modifiers,
9816                use_key_keyword,
9817                expression,
9818                index_type,
9819                granularity,
9820            } => {
9821                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
9822                if expression.is_some() {
9823                    self.write_keyword("INDEX");
9824                    if let Some(ref n) = name {
9825                        self.write_space();
9826                        self.generate_identifier(n)?;
9827                    }
9828                    if let Some(ref expr) = expression {
9829                        self.write_space();
9830                        self.generate_expression(expr)?;
9831                    }
9832                    if let Some(ref idx_type) = index_type {
9833                        self.write_space();
9834                        self.write_keyword("TYPE");
9835                        self.write_space();
9836                        self.generate_expression(idx_type)?;
9837                    }
9838                    if let Some(ref gran) = granularity {
9839                        self.write_space();
9840                        self.write_keyword("GRANULARITY");
9841                        self.write_space();
9842                        self.generate_expression(gran)?;
9843                    }
9844                } else {
9845                    // Standard INDEX syntax
9846                    // Determine the index keyword to use
9847                    // MySQL normalizes KEY to INDEX
9848                    use crate::dialects::DialectType;
9849                    let index_keyword = if *use_key_keyword
9850                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
9851                    {
9852                        "KEY"
9853                    } else {
9854                        "INDEX"
9855                    };
9856
9857                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
9858                    if let Some(ref k) = kind {
9859                        self.write_keyword(k);
9860                        // For UNIQUE, don't add INDEX/KEY keyword
9861                        if k != "UNIQUE" {
9862                            self.write_space();
9863                            self.write_keyword(index_keyword);
9864                        }
9865                    } else {
9866                        self.write_keyword(index_keyword);
9867                    }
9868
9869                    // Output USING before name if using_before_columns is true and there's no name
9870                    if modifiers.using_before_columns && name.is_none() {
9871                        if let Some(ref using) = modifiers.using {
9872                            self.write_space();
9873                            self.write_keyword("USING");
9874                            self.write_space();
9875                            self.write_keyword(using);
9876                        }
9877                    }
9878
9879                    // Output index name if present
9880                    if let Some(ref n) = name {
9881                        self.write_space();
9882                        self.generate_identifier(n)?;
9883                    }
9884
9885                    // Output USING after name but before columns if using_before_columns and there's a name
9886                    if modifiers.using_before_columns && name.is_some() {
9887                        if let Some(ref using) = modifiers.using {
9888                            self.write_space();
9889                            self.write_keyword("USING");
9890                            self.write_space();
9891                            self.write_keyword(using);
9892                        }
9893                    }
9894
9895                    // Output columns
9896                    self.write(" (");
9897                    for (i, col) in columns.iter().enumerate() {
9898                        if i > 0 {
9899                            self.write(", ");
9900                        }
9901                        self.generate_identifier(col)?;
9902                    }
9903                    self.write(")");
9904
9905                    // Output USING after columns if not using_before_columns
9906                    if !modifiers.using_before_columns {
9907                        if let Some(ref using) = modifiers.using {
9908                            self.write_space();
9909                            self.write_keyword("USING");
9910                            self.write_space();
9911                            self.write_keyword(using);
9912                        }
9913                    }
9914
9915                    // Output other constraint modifiers (but skip USING since we already handled it)
9916                    self.generate_constraint_modifiers_without_using(modifiers);
9917                }
9918            }
9919            TableConstraint::Projection { name, expression } => {
9920                // ClickHouse: PROJECTION name (SELECT ...)
9921                self.write_keyword("PROJECTION");
9922                self.write_space();
9923                self.generate_identifier(name)?;
9924                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
9925                    if let Expression::Raw(raw) = expression {
9926                        if raw
9927                            .sql
9928                            .trim_start()
9929                            .to_ascii_uppercase()
9930                            .starts_with("INDEX ")
9931                        {
9932                            self.write_space();
9933                            self.write(raw.sql.trim());
9934                            return Ok(());
9935                        }
9936                    }
9937                }
9938                self.write(" (");
9939                self.generate_expression(expression)?;
9940                self.write(")");
9941            }
9942            TableConstraint::Like { source, options } => {
9943                self.write_keyword("LIKE");
9944                self.write_space();
9945                self.generate_table(source)?;
9946                for (action, prop) in options {
9947                    self.write_space();
9948                    match action {
9949                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
9950                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
9951                    }
9952                    self.write_space();
9953                    self.write_keyword(prop);
9954                }
9955            }
9956            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
9957                self.write_keyword("PERIOD FOR SYSTEM_TIME");
9958                self.write(" (");
9959                self.generate_identifier(start_col)?;
9960                self.write(", ");
9961                self.generate_identifier(end_col)?;
9962                self.write(")");
9963            }
9964            TableConstraint::Exclude {
9965                name,
9966                using,
9967                elements,
9968                include_columns,
9969                where_clause,
9970                with_params,
9971                using_index_tablespace,
9972                modifiers: _,
9973            } => {
9974                if let Some(ref n) = name {
9975                    self.write_keyword("CONSTRAINT");
9976                    self.write_space();
9977                    self.generate_identifier(n)?;
9978                    self.write_space();
9979                }
9980                self.write_keyword("EXCLUDE");
9981                if let Some(ref method) = using {
9982                    self.write_space();
9983                    self.write_keyword("USING");
9984                    self.write_space();
9985                    self.write(method);
9986                    self.write("(");
9987                } else {
9988                    self.write(" (");
9989                }
9990                for (i, elem) in elements.iter().enumerate() {
9991                    if i > 0 {
9992                        self.write(", ");
9993                    }
9994                    self.write(&elem.expression);
9995                    self.write_space();
9996                    self.write_keyword("WITH");
9997                    self.write_space();
9998                    self.write(&elem.operator);
9999                }
10000                self.write(")");
10001                if !include_columns.is_empty() {
10002                    self.write_space();
10003                    self.write_keyword("INCLUDE");
10004                    self.write(" (");
10005                    for (i, col) in include_columns.iter().enumerate() {
10006                        if i > 0 {
10007                            self.write(", ");
10008                        }
10009                        self.generate_identifier(col)?;
10010                    }
10011                    self.write(")");
10012                }
10013                if !with_params.is_empty() {
10014                    self.write_space();
10015                    self.write_keyword("WITH");
10016                    self.write(" (");
10017                    for (i, (key, val)) in with_params.iter().enumerate() {
10018                        if i > 0 {
10019                            self.write(", ");
10020                        }
10021                        self.write(key);
10022                        self.write("=");
10023                        self.write(val);
10024                    }
10025                    self.write(")");
10026                }
10027                if let Some(ref tablespace) = using_index_tablespace {
10028                    self.write_space();
10029                    self.write_keyword("USING INDEX TABLESPACE");
10030                    self.write_space();
10031                    self.write(tablespace);
10032                }
10033                if let Some(ref where_expr) = where_clause {
10034                    self.write_space();
10035                    self.write_keyword("WHERE");
10036                    self.write(" (");
10037                    self.generate_expression(where_expr)?;
10038                    self.write(")");
10039                }
10040            }
10041            TableConstraint::Tags(tags) => {
10042                self.write_keyword("TAG");
10043                self.write(" (");
10044                for (i, expr) in tags.expressions.iter().enumerate() {
10045                    if i > 0 {
10046                        self.write(", ");
10047                    }
10048                    self.generate_expression(expr)?;
10049                }
10050                self.write(")");
10051            }
10052            TableConstraint::InitiallyDeferred { deferred } => {
10053                self.write_keyword("INITIALLY");
10054                self.write_space();
10055                if *deferred {
10056                    self.write_keyword("DEFERRED");
10057                } else {
10058                    self.write_keyword("IMMEDIATE");
10059                }
10060            }
10061        }
10062        Ok(())
10063    }
10064
10065    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
10066        // Output USING BTREE/HASH (MySQL) - comes first
10067        if let Some(using) = &modifiers.using {
10068            self.write_space();
10069            self.write_keyword("USING");
10070            self.write_space();
10071            self.write_keyword(using);
10072        }
10073        // Output ENFORCED/NOT ENFORCED
10074        if let Some(enforced) = modifiers.enforced {
10075            self.write_space();
10076            if enforced {
10077                self.write_keyword("ENFORCED");
10078            } else {
10079                self.write_keyword("NOT ENFORCED");
10080            }
10081        }
10082        // Output DEFERRABLE/NOT DEFERRABLE
10083        if let Some(deferrable) = modifiers.deferrable {
10084            self.write_space();
10085            if deferrable {
10086                self.write_keyword("DEFERRABLE");
10087            } else {
10088                self.write_keyword("NOT DEFERRABLE");
10089            }
10090        }
10091        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
10092        if let Some(initially_deferred) = modifiers.initially_deferred {
10093            self.write_space();
10094            if initially_deferred {
10095                self.write_keyword("INITIALLY DEFERRED");
10096            } else {
10097                self.write_keyword("INITIALLY IMMEDIATE");
10098            }
10099        }
10100        // Output NORELY
10101        if modifiers.norely {
10102            self.write_space();
10103            self.write_keyword("NORELY");
10104        }
10105        // Output RELY
10106        if modifiers.rely {
10107            self.write_space();
10108            self.write_keyword("RELY");
10109        }
10110        // Output NOT VALID (PostgreSQL)
10111        if modifiers.not_valid {
10112            self.write_space();
10113            self.write_keyword("NOT VALID");
10114        }
10115        // Output ON CONFLICT (SQLite)
10116        if let Some(on_conflict) = &modifiers.on_conflict {
10117            self.write_space();
10118            self.write_keyword("ON CONFLICT");
10119            self.write_space();
10120            self.write_keyword(on_conflict);
10121        }
10122        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
10123        if !modifiers.with_options.is_empty() {
10124            self.write_space();
10125            self.write_keyword("WITH");
10126            self.write(" (");
10127            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
10128                if i > 0 {
10129                    self.write(", ");
10130                }
10131                self.write(key);
10132                self.write("=");
10133                self.write(value);
10134            }
10135            self.write(")");
10136        }
10137        // Output TSQL ON filegroup
10138        if let Some(ref fg) = modifiers.on_filegroup {
10139            self.write_space();
10140            self.write_keyword("ON");
10141            self.write_space();
10142            let _ = self.generate_identifier(fg);
10143        }
10144    }
10145
10146    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
10147    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
10148        // Output ENFORCED/NOT ENFORCED
10149        if let Some(enforced) = modifiers.enforced {
10150            self.write_space();
10151            if enforced {
10152                self.write_keyword("ENFORCED");
10153            } else {
10154                self.write_keyword("NOT ENFORCED");
10155            }
10156        }
10157        // Output DEFERRABLE/NOT DEFERRABLE
10158        if let Some(deferrable) = modifiers.deferrable {
10159            self.write_space();
10160            if deferrable {
10161                self.write_keyword("DEFERRABLE");
10162            } else {
10163                self.write_keyword("NOT DEFERRABLE");
10164            }
10165        }
10166        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
10167        if let Some(initially_deferred) = modifiers.initially_deferred {
10168            self.write_space();
10169            if initially_deferred {
10170                self.write_keyword("INITIALLY DEFERRED");
10171            } else {
10172                self.write_keyword("INITIALLY IMMEDIATE");
10173            }
10174        }
10175        // Output NORELY
10176        if modifiers.norely {
10177            self.write_space();
10178            self.write_keyword("NORELY");
10179        }
10180        // Output RELY
10181        if modifiers.rely {
10182            self.write_space();
10183            self.write_keyword("RELY");
10184        }
10185        // Output NOT VALID (PostgreSQL)
10186        if modifiers.not_valid {
10187            self.write_space();
10188            self.write_keyword("NOT VALID");
10189        }
10190        // Output ON CONFLICT (SQLite)
10191        if let Some(on_conflict) = &modifiers.on_conflict {
10192            self.write_space();
10193            self.write_keyword("ON CONFLICT");
10194            self.write_space();
10195            self.write_keyword(on_conflict);
10196        }
10197        // Output MySQL index-specific modifiers
10198        self.generate_index_specific_modifiers(modifiers);
10199    }
10200
10201    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
10202    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
10203        if let Some(ref comment) = modifiers.comment {
10204            self.write_space();
10205            self.write_keyword("COMMENT");
10206            self.write(" '");
10207            self.write(comment);
10208            self.write("'");
10209        }
10210        if let Some(visible) = modifiers.visible {
10211            self.write_space();
10212            if visible {
10213                self.write_keyword("VISIBLE");
10214            } else {
10215                self.write_keyword("INVISIBLE");
10216            }
10217        }
10218        if let Some(ref attr) = modifiers.engine_attribute {
10219            self.write_space();
10220            self.write_keyword("ENGINE_ATTRIBUTE");
10221            self.write(" = '");
10222            self.write(attr);
10223            self.write("'");
10224        }
10225        if let Some(ref parser) = modifiers.with_parser {
10226            self.write_space();
10227            self.write_keyword("WITH PARSER");
10228            self.write_space();
10229            self.write(parser);
10230        }
10231    }
10232
10233    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
10234        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
10235        if !fk_ref.match_after_actions {
10236            if let Some(ref match_type) = fk_ref.match_type {
10237                self.write_space();
10238                self.write_keyword("MATCH");
10239                self.write_space();
10240                match match_type {
10241                    MatchType::Full => self.write_keyword("FULL"),
10242                    MatchType::Partial => self.write_keyword("PARTIAL"),
10243                    MatchType::Simple => self.write_keyword("SIMPLE"),
10244                }
10245            }
10246        }
10247
10248        // Output ON UPDATE and ON DELETE in the original order
10249        if fk_ref.on_update_first {
10250            if let Some(ref action) = fk_ref.on_update {
10251                self.write_space();
10252                self.write_keyword("ON UPDATE");
10253                self.write_space();
10254                self.generate_referential_action(action);
10255            }
10256            if let Some(ref action) = fk_ref.on_delete {
10257                self.write_space();
10258                self.write_keyword("ON DELETE");
10259                self.write_space();
10260                self.generate_referential_action(action);
10261            }
10262        } else {
10263            if let Some(ref action) = fk_ref.on_delete {
10264                self.write_space();
10265                self.write_keyword("ON DELETE");
10266                self.write_space();
10267                self.generate_referential_action(action);
10268            }
10269            if let Some(ref action) = fk_ref.on_update {
10270                self.write_space();
10271                self.write_keyword("ON UPDATE");
10272                self.write_space();
10273                self.generate_referential_action(action);
10274            }
10275        }
10276
10277        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
10278        if fk_ref.match_after_actions {
10279            if let Some(ref match_type) = fk_ref.match_type {
10280                self.write_space();
10281                self.write_keyword("MATCH");
10282                self.write_space();
10283                match match_type {
10284                    MatchType::Full => self.write_keyword("FULL"),
10285                    MatchType::Partial => self.write_keyword("PARTIAL"),
10286                    MatchType::Simple => self.write_keyword("SIMPLE"),
10287                }
10288            }
10289        }
10290
10291        // DEFERRABLE / NOT DEFERRABLE
10292        if let Some(deferrable) = fk_ref.deferrable {
10293            self.write_space();
10294            if deferrable {
10295                self.write_keyword("DEFERRABLE");
10296            } else {
10297                self.write_keyword("NOT DEFERRABLE");
10298            }
10299        }
10300
10301        Ok(())
10302    }
10303
10304    fn generate_referential_action(&mut self, action: &ReferentialAction) {
10305        match action {
10306            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
10307            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
10308            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
10309            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
10310            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
10311        }
10312    }
10313
10314    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
10315        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
10316        if let Some(ref object_id_args) = dt.object_id_args {
10317            if matches!(
10318                self.config.dialect,
10319                Some(crate::dialects::DialectType::TSQL)
10320                    | Some(crate::dialects::DialectType::Fabric)
10321            ) {
10322                self.write_keyword("IF NOT OBJECT_ID");
10323                self.write("(");
10324                self.write(object_id_args);
10325                self.write(")");
10326                self.write_space();
10327                self.write_keyword("IS NULL BEGIN DROP TABLE");
10328                self.write_space();
10329                for (i, table) in dt.names.iter().enumerate() {
10330                    if i > 0 {
10331                        self.write(", ");
10332                    }
10333                    self.generate_table(table)?;
10334                }
10335                self.write("; ");
10336                self.write_keyword("END");
10337                return Ok(());
10338            }
10339        }
10340
10341        // Athena: DROP TABLE uses Hive engine (backticks)
10342        let saved_athena_hive_context = self.athena_hive_context;
10343        if matches!(
10344            self.config.dialect,
10345            Some(crate::dialects::DialectType::Athena)
10346        ) {
10347            self.athena_hive_context = true;
10348        }
10349
10350        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
10351        for comment in &dt.leading_comments {
10352            self.write_formatted_comment(comment);
10353            self.write_space();
10354        }
10355        if dt.iceberg {
10356            self.write_keyword("DROP ICEBERG TABLE");
10357        } else {
10358            self.write_keyword("DROP TABLE");
10359        }
10360
10361        if dt.if_exists {
10362            self.write_space();
10363            self.write_keyword("IF EXISTS");
10364        }
10365
10366        self.write_space();
10367        for (i, table) in dt.names.iter().enumerate() {
10368            if i > 0 {
10369                self.write(", ");
10370            }
10371            self.generate_table(table)?;
10372        }
10373
10374        if dt.cascade_constraints {
10375            self.write_space();
10376            self.write_keyword("CASCADE CONSTRAINTS");
10377        } else if dt.cascade {
10378            self.write_space();
10379            self.write_keyword("CASCADE");
10380        }
10381
10382        if dt.restrict {
10383            self.write_space();
10384            self.write_keyword("RESTRICT");
10385        }
10386
10387        if dt.purge {
10388            self.write_space();
10389            self.write_keyword("PURGE");
10390        }
10391
10392        if dt.sync {
10393            self.write_space();
10394            self.write_keyword("SYNC");
10395        }
10396
10397        // Restore Athena Hive context
10398        self.athena_hive_context = saved_athena_hive_context;
10399
10400        Ok(())
10401    }
10402
10403    fn generate_undrop(&mut self, u: &Undrop) -> Result<()> {
10404        self.write_keyword("UNDROP");
10405        self.write_space();
10406        self.write_keyword(&u.kind);
10407        if u.if_exists {
10408            self.write_space();
10409            self.write_keyword("IF EXISTS");
10410        }
10411        self.write_space();
10412        self.generate_table(&u.name)?;
10413        if let Some(rename_to) = &u.rename_to {
10414            self.write_space();
10415            self.write_keyword("RENAME TO");
10416            self.write_space();
10417            self.generate_table(rename_to)?;
10418        }
10419        Ok(())
10420    }
10421
10422    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
10423        // Athena: ALTER TABLE uses Hive engine (backticks)
10424        let saved_athena_hive_context = self.athena_hive_context;
10425        if matches!(
10426            self.config.dialect,
10427            Some(crate::dialects::DialectType::Athena)
10428        ) {
10429            self.athena_hive_context = true;
10430        }
10431
10432        self.write_keyword("ALTER");
10433        // Write table modifier (e.g., ICEBERG) unless target is DuckDB
10434        if let Some(ref modifier) = at.table_modifier {
10435            if !matches!(
10436                self.config.dialect,
10437                Some(crate::dialects::DialectType::DuckDB)
10438            ) {
10439                self.write_space();
10440                self.write_keyword(modifier);
10441            }
10442        }
10443        self.write(" ");
10444        self.write_keyword("TABLE");
10445        if at.if_exists {
10446            self.write_space();
10447            self.write_keyword("IF EXISTS");
10448        }
10449        self.write_space();
10450        self.generate_table(&at.name)?;
10451
10452        // ClickHouse: ON CLUSTER clause
10453        if let Some(ref on_cluster) = at.on_cluster {
10454            self.write_space();
10455            self.generate_on_cluster(on_cluster)?;
10456        }
10457
10458        // Hive: PARTITION(key=value, ...) clause
10459        if let Some(ref partition) = at.partition {
10460            self.write_space();
10461            self.write_keyword("PARTITION");
10462            self.write("(");
10463            for (i, (key, value)) in partition.iter().enumerate() {
10464                if i > 0 {
10465                    self.write(", ");
10466                }
10467                self.generate_identifier(key)?;
10468                self.write(" = ");
10469                self.generate_expression(value)?;
10470            }
10471            self.write(")");
10472        }
10473
10474        // TSQL: WITH CHECK / WITH NOCHECK modifier
10475        if let Some(ref with_check) = at.with_check {
10476            self.write_space();
10477            self.write_keyword(with_check);
10478        }
10479
10480        if self.config.pretty {
10481            // In pretty mode, format actions with newlines and indentation
10482            self.write_newline();
10483            self.indent_level += 1;
10484            for (i, action) in at.actions.iter().enumerate() {
10485                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10486                let is_continuation = i > 0
10487                    && matches!(
10488                        (&at.actions[i - 1], action),
10489                        (
10490                            AlterTableAction::AddColumn { .. },
10491                            AlterTableAction::AddColumn { .. }
10492                        ) | (
10493                            AlterTableAction::AddConstraint(_),
10494                            AlterTableAction::AddConstraint(_)
10495                        )
10496                    );
10497                if i > 0 {
10498                    self.write(",");
10499                    self.write_newline();
10500                }
10501                self.write_indent();
10502                self.generate_alter_action_with_continuation(action, is_continuation)?;
10503            }
10504            self.indent_level -= 1;
10505        } else {
10506            for (i, action) in at.actions.iter().enumerate() {
10507                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10508                let is_continuation = i > 0
10509                    && matches!(
10510                        (&at.actions[i - 1], action),
10511                        (
10512                            AlterTableAction::AddColumn { .. },
10513                            AlterTableAction::AddColumn { .. }
10514                        ) | (
10515                            AlterTableAction::AddConstraint(_),
10516                            AlterTableAction::AddConstraint(_)
10517                        )
10518                    );
10519                if i > 0 {
10520                    self.write(",");
10521                }
10522                self.write_space();
10523                self.generate_alter_action_with_continuation(action, is_continuation)?;
10524            }
10525        }
10526
10527        // MySQL ALTER TABLE trailing options
10528        if let Some(ref algorithm) = at.algorithm {
10529            self.write(", ");
10530            self.write_keyword("ALGORITHM");
10531            self.write("=");
10532            self.write_keyword(algorithm);
10533        }
10534        if let Some(ref lock) = at.lock {
10535            self.write(", ");
10536            self.write_keyword("LOCK");
10537            self.write("=");
10538            self.write_keyword(lock);
10539        }
10540
10541        // Restore Athena Hive context
10542        self.athena_hive_context = saved_athena_hive_context;
10543
10544        Ok(())
10545    }
10546
10547    fn generate_alter_action_with_continuation(
10548        &mut self,
10549        action: &AlterTableAction,
10550        is_continuation: bool,
10551    ) -> Result<()> {
10552        match action {
10553            AlterTableAction::AddColumn {
10554                column,
10555                if_not_exists,
10556                position,
10557            } => {
10558                use crate::dialects::DialectType;
10559                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
10560                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
10561                // For other dialects, repeat ADD COLUMN for each
10562                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
10563                let is_tsql_like = matches!(
10564                    self.config.dialect,
10565                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
10566                );
10567                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
10568                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
10569
10570                if is_continuation && (is_snowflake || is_tsql_like) {
10571                    // Don't write ADD keyword for continuation in Snowflake/TSQL
10572                } else if is_snowflake {
10573                    self.write_keyword("ADD");
10574                    self.write_space();
10575                } else if is_athena {
10576                    // Athena uses ADD COLUMNS (col_def) syntax
10577                    self.write_keyword("ADD COLUMNS");
10578                    self.write(" (");
10579                } else if self.config.alter_table_include_column_keyword {
10580                    self.write_keyword("ADD COLUMN");
10581                    self.write_space();
10582                } else {
10583                    // Dialects like Oracle and TSQL don't use COLUMN keyword
10584                    self.write_keyword("ADD");
10585                    self.write_space();
10586                }
10587
10588                if *if_not_exists {
10589                    self.write_keyword("IF NOT EXISTS");
10590                    self.write_space();
10591                }
10592                self.generate_column_def(column)?;
10593
10594                // Close parenthesis for Athena
10595                if is_athena {
10596                    self.write(")");
10597                }
10598
10599                // Column position (FIRST or AFTER)
10600                if let Some(pos) = position {
10601                    self.write_space();
10602                    match pos {
10603                        ColumnPosition::First => self.write_keyword("FIRST"),
10604                        ColumnPosition::After(col_name) => {
10605                            self.write_keyword("AFTER");
10606                            self.write_space();
10607                            self.generate_identifier(col_name)?;
10608                        }
10609                    }
10610                }
10611            }
10612            AlterTableAction::DropColumn {
10613                name,
10614                if_exists,
10615                cascade,
10616            } => {
10617                self.write_keyword("DROP COLUMN");
10618                if *if_exists {
10619                    self.write_space();
10620                    self.write_keyword("IF EXISTS");
10621                }
10622                self.write_space();
10623                self.generate_identifier(name)?;
10624                if *cascade {
10625                    self.write_space();
10626                    self.write_keyword("CASCADE");
10627                }
10628            }
10629            AlterTableAction::DropColumns { names } => {
10630                self.write_keyword("DROP COLUMNS");
10631                self.write(" (");
10632                for (i, name) in names.iter().enumerate() {
10633                    if i > 0 {
10634                        self.write(", ");
10635                    }
10636                    self.generate_identifier(name)?;
10637                }
10638                self.write(")");
10639            }
10640            AlterTableAction::RenameColumn {
10641                old_name,
10642                new_name,
10643                if_exists,
10644            } => {
10645                self.write_keyword("RENAME COLUMN");
10646                if *if_exists {
10647                    self.write_space();
10648                    self.write_keyword("IF EXISTS");
10649                }
10650                self.write_space();
10651                self.generate_identifier(old_name)?;
10652                self.write_space();
10653                self.write_keyword("TO");
10654                self.write_space();
10655                self.generate_identifier(new_name)?;
10656            }
10657            AlterTableAction::AlterColumn {
10658                name,
10659                action,
10660                use_modify_keyword,
10661            } => {
10662                use crate::dialects::DialectType;
10663                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
10664                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
10665                let use_modify = *use_modify_keyword
10666                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
10667                        && matches!(action, AlterColumnAction::SetDataType { .. }));
10668                if use_modify {
10669                    self.write_keyword("MODIFY COLUMN");
10670                    self.write_space();
10671                    self.generate_identifier(name)?;
10672                    // For MODIFY COLUMN, output the type directly
10673                    if let AlterColumnAction::SetDataType {
10674                        data_type,
10675                        using: _,
10676                        collate,
10677                    } = action
10678                    {
10679                        self.write_space();
10680                        self.generate_data_type(data_type)?;
10681                        // Output COLLATE clause if present
10682                        if let Some(collate_name) = collate {
10683                            self.write_space();
10684                            self.write_keyword("COLLATE");
10685                            self.write_space();
10686                            // Output as single-quoted string
10687                            self.write(&format!("'{}'", collate_name));
10688                        }
10689                    } else {
10690                        self.write_space();
10691                        self.generate_alter_column_action(action)?;
10692                    }
10693                } else if matches!(self.config.dialect, Some(DialectType::Hive))
10694                    && matches!(action, AlterColumnAction::SetDataType { .. })
10695                {
10696                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
10697                    self.write_keyword("CHANGE COLUMN");
10698                    self.write_space();
10699                    self.generate_identifier(name)?;
10700                    self.write_space();
10701                    self.generate_identifier(name)?;
10702                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
10703                        self.write_space();
10704                        self.generate_data_type(data_type)?;
10705                    }
10706                } else {
10707                    self.write_keyword("ALTER COLUMN");
10708                    self.write_space();
10709                    self.generate_identifier(name)?;
10710                    self.write_space();
10711                    self.generate_alter_column_action(action)?;
10712                }
10713            }
10714            AlterTableAction::RenameTable(new_name) => {
10715                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
10716                let mysql_like = matches!(
10717                    self.config.dialect,
10718                    Some(DialectType::MySQL)
10719                        | Some(DialectType::Doris)
10720                        | Some(DialectType::StarRocks)
10721                        | Some(DialectType::SingleStore)
10722                );
10723                if mysql_like {
10724                    self.write_keyword("RENAME");
10725                } else {
10726                    self.write_keyword("RENAME TO");
10727                }
10728                self.write_space();
10729                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
10730                let rename_table_with_db = !matches!(
10731                    self.config.dialect,
10732                    Some(DialectType::Doris)
10733                        | Some(DialectType::DuckDB)
10734                        | Some(DialectType::BigQuery)
10735                        | Some(DialectType::PostgreSQL)
10736                );
10737                if !rename_table_with_db {
10738                    let mut stripped = new_name.clone();
10739                    stripped.schema = None;
10740                    stripped.catalog = None;
10741                    self.generate_table(&stripped)?;
10742                } else {
10743                    self.generate_table(new_name)?;
10744                }
10745            }
10746            AlterTableAction::AddConstraint(constraint) => {
10747                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
10748                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
10749                if !is_continuation {
10750                    self.write_keyword("ADD");
10751                    self.write_space();
10752                }
10753                self.generate_table_constraint(constraint)?;
10754            }
10755            AlterTableAction::DropConstraint { name, if_exists } => {
10756                self.write_keyword("DROP CONSTRAINT");
10757                if *if_exists {
10758                    self.write_space();
10759                    self.write_keyword("IF EXISTS");
10760                }
10761                self.write_space();
10762                self.generate_identifier(name)?;
10763            }
10764            AlterTableAction::DropForeignKey { name } => {
10765                self.write_keyword("DROP FOREIGN KEY");
10766                self.write_space();
10767                self.generate_identifier(name)?;
10768            }
10769            AlterTableAction::DropPartition {
10770                partitions,
10771                if_exists,
10772            } => {
10773                self.write_keyword("DROP");
10774                if *if_exists {
10775                    self.write_space();
10776                    self.write_keyword("IF EXISTS");
10777                }
10778                for (i, partition) in partitions.iter().enumerate() {
10779                    if i > 0 {
10780                        self.write(",");
10781                    }
10782                    self.write_space();
10783                    self.write_keyword("PARTITION");
10784                    // Check for special ClickHouse partition formats
10785                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
10786                        // ClickHouse: PARTITION <expression>
10787                        self.write_space();
10788                        self.generate_expression(&partition[0].1)?;
10789                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
10790                        // ClickHouse: PARTITION ALL
10791                        self.write_space();
10792                        self.write_keyword("ALL");
10793                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
10794                        // ClickHouse: PARTITION ID 'string'
10795                        self.write_space();
10796                        self.write_keyword("ID");
10797                        self.write_space();
10798                        self.generate_expression(&partition[0].1)?;
10799                    } else {
10800                        // Standard SQL: PARTITION(key=value, ...)
10801                        self.write("(");
10802                        for (j, (key, value)) in partition.iter().enumerate() {
10803                            if j > 0 {
10804                                self.write(", ");
10805                            }
10806                            self.generate_identifier(key)?;
10807                            self.write(" = ");
10808                            self.generate_expression(value)?;
10809                        }
10810                        self.write(")");
10811                    }
10812                }
10813            }
10814            AlterTableAction::Delete { where_clause } => {
10815                self.write_keyword("DELETE");
10816                self.write_space();
10817                self.write_keyword("WHERE");
10818                self.write_space();
10819                self.generate_expression(where_clause)?;
10820            }
10821            AlterTableAction::SwapWith(target) => {
10822                self.write_keyword("SWAP WITH");
10823                self.write_space();
10824                self.generate_table(target)?;
10825            }
10826            AlterTableAction::SetProperty { properties } => {
10827                use crate::dialects::DialectType;
10828                self.write_keyword("SET");
10829                // Trino/Presto use SET PROPERTIES syntax with spaces around =
10830                let is_trino_presto = matches!(
10831                    self.config.dialect,
10832                    Some(DialectType::Trino) | Some(DialectType::Presto)
10833                );
10834                if is_trino_presto {
10835                    self.write_space();
10836                    self.write_keyword("PROPERTIES");
10837                }
10838                let eq = if is_trino_presto { " = " } else { "=" };
10839                for (i, (key, value)) in properties.iter().enumerate() {
10840                    if i > 0 {
10841                        self.write(",");
10842                    }
10843                    self.write_space();
10844                    // Handle quoted property names for Trino
10845                    if key.contains(' ') {
10846                        self.generate_string_literal(key)?;
10847                    } else {
10848                        self.write(key);
10849                    }
10850                    self.write(eq);
10851                    self.generate_expression(value)?;
10852                }
10853            }
10854            AlterTableAction::UnsetProperty { properties } => {
10855                self.write_keyword("UNSET");
10856                for (i, name) in properties.iter().enumerate() {
10857                    if i > 0 {
10858                        self.write(",");
10859                    }
10860                    self.write_space();
10861                    self.write(name);
10862                }
10863            }
10864            AlterTableAction::ClusterBy { expressions } => {
10865                self.write_keyword("CLUSTER BY");
10866                self.write(" (");
10867                for (i, expr) in expressions.iter().enumerate() {
10868                    if i > 0 {
10869                        self.write(", ");
10870                    }
10871                    self.generate_expression(expr)?;
10872                }
10873                self.write(")");
10874            }
10875            AlterTableAction::SetTag { expressions } => {
10876                self.write_keyword("SET TAG");
10877                for (i, (key, value)) in expressions.iter().enumerate() {
10878                    if i > 0 {
10879                        self.write(",");
10880                    }
10881                    self.write_space();
10882                    self.write(key);
10883                    self.write(" = ");
10884                    self.generate_expression(value)?;
10885                }
10886            }
10887            AlterTableAction::UnsetTag { names } => {
10888                self.write_keyword("UNSET TAG");
10889                for (i, name) in names.iter().enumerate() {
10890                    if i > 0 {
10891                        self.write(",");
10892                    }
10893                    self.write_space();
10894                    self.write(name);
10895                }
10896            }
10897            AlterTableAction::SetOptions { expressions } => {
10898                self.write_keyword("SET");
10899                self.write(" (");
10900                for (i, expr) in expressions.iter().enumerate() {
10901                    if i > 0 {
10902                        self.write(", ");
10903                    }
10904                    self.generate_expression(expr)?;
10905                }
10906                self.write(")");
10907            }
10908            AlterTableAction::AlterIndex { name, visible } => {
10909                self.write_keyword("ALTER INDEX");
10910                self.write_space();
10911                self.generate_identifier(name)?;
10912                self.write_space();
10913                if *visible {
10914                    self.write_keyword("VISIBLE");
10915                } else {
10916                    self.write_keyword("INVISIBLE");
10917                }
10918            }
10919            AlterTableAction::SetAttribute { attribute } => {
10920                self.write_keyword("SET");
10921                self.write_space();
10922                self.write_keyword(attribute);
10923            }
10924            AlterTableAction::SetStageFileFormat { options } => {
10925                self.write_keyword("SET");
10926                self.write_space();
10927                self.write_keyword("STAGE_FILE_FORMAT");
10928                self.write(" = (");
10929                if let Some(opts) = options {
10930                    self.generate_space_separated_properties(opts)?;
10931                }
10932                self.write(")");
10933            }
10934            AlterTableAction::SetStageCopyOptions { options } => {
10935                self.write_keyword("SET");
10936                self.write_space();
10937                self.write_keyword("STAGE_COPY_OPTIONS");
10938                self.write(" = (");
10939                if let Some(opts) = options {
10940                    self.generate_space_separated_properties(opts)?;
10941                }
10942                self.write(")");
10943            }
10944            AlterTableAction::AddColumns { columns, cascade } => {
10945                // Oracle uses ADD (...) without COLUMNS keyword
10946                // Hive/Spark uses ADD COLUMNS (...)
10947                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
10948                if is_oracle {
10949                    self.write_keyword("ADD");
10950                } else {
10951                    self.write_keyword("ADD COLUMNS");
10952                }
10953                self.write(" (");
10954                for (i, col) in columns.iter().enumerate() {
10955                    if i > 0 {
10956                        self.write(", ");
10957                    }
10958                    self.generate_column_def(col)?;
10959                }
10960                self.write(")");
10961                if *cascade {
10962                    self.write_space();
10963                    self.write_keyword("CASCADE");
10964                }
10965            }
10966            AlterTableAction::ChangeColumn {
10967                old_name,
10968                new_name,
10969                data_type,
10970                comment,
10971                cascade,
10972            } => {
10973                use crate::dialects::DialectType;
10974                let is_spark = matches!(
10975                    self.config.dialect,
10976                    Some(DialectType::Spark) | Some(DialectType::Databricks)
10977                );
10978                let is_rename = old_name.name != new_name.name;
10979
10980                if is_spark {
10981                    if is_rename {
10982                        // Spark: RENAME COLUMN old TO new
10983                        self.write_keyword("RENAME COLUMN");
10984                        self.write_space();
10985                        self.generate_identifier(old_name)?;
10986                        self.write_space();
10987                        self.write_keyword("TO");
10988                        self.write_space();
10989                        self.generate_identifier(new_name)?;
10990                    } else if comment.is_some() {
10991                        // Spark: ALTER COLUMN old COMMENT 'comment'
10992                        self.write_keyword("ALTER COLUMN");
10993                        self.write_space();
10994                        self.generate_identifier(old_name)?;
10995                        self.write_space();
10996                        self.write_keyword("COMMENT");
10997                        self.write_space();
10998                        self.write("'");
10999                        self.write(comment.as_ref().unwrap());
11000                        self.write("'");
11001                    } else if data_type.is_some() {
11002                        // Spark: ALTER COLUMN old TYPE data_type
11003                        self.write_keyword("ALTER COLUMN");
11004                        self.write_space();
11005                        self.generate_identifier(old_name)?;
11006                        self.write_space();
11007                        self.write_keyword("TYPE");
11008                        self.write_space();
11009                        self.generate_data_type(data_type.as_ref().unwrap())?;
11010                    } else {
11011                        // Fallback to CHANGE COLUMN
11012                        self.write_keyword("CHANGE COLUMN");
11013                        self.write_space();
11014                        self.generate_identifier(old_name)?;
11015                        self.write_space();
11016                        self.generate_identifier(new_name)?;
11017                    }
11018                } else {
11019                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
11020                    if data_type.is_some() {
11021                        self.write_keyword("CHANGE COLUMN");
11022                    } else {
11023                        self.write_keyword("CHANGE");
11024                    }
11025                    self.write_space();
11026                    self.generate_identifier(old_name)?;
11027                    self.write_space();
11028                    self.generate_identifier(new_name)?;
11029                    if let Some(ref dt) = data_type {
11030                        self.write_space();
11031                        self.generate_data_type(dt)?;
11032                    }
11033                    if let Some(ref c) = comment {
11034                        self.write_space();
11035                        self.write_keyword("COMMENT");
11036                        self.write_space();
11037                        self.write("'");
11038                        self.write(c);
11039                        self.write("'");
11040                    }
11041                    if *cascade {
11042                        self.write_space();
11043                        self.write_keyword("CASCADE");
11044                    }
11045                }
11046            }
11047            AlterTableAction::AddPartition {
11048                partition,
11049                if_not_exists,
11050                location,
11051            } => {
11052                self.write_keyword("ADD");
11053                self.write_space();
11054                if *if_not_exists {
11055                    self.write_keyword("IF NOT EXISTS");
11056                    self.write_space();
11057                }
11058                self.generate_expression(partition)?;
11059                if let Some(ref loc) = location {
11060                    self.write_space();
11061                    self.write_keyword("LOCATION");
11062                    self.write_space();
11063                    self.generate_expression(loc)?;
11064                }
11065            }
11066            AlterTableAction::AlterSortKey {
11067                this,
11068                expressions,
11069                compound,
11070            } => {
11071                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
11072                self.write_keyword("ALTER");
11073                if *compound {
11074                    self.write_space();
11075                    self.write_keyword("COMPOUND");
11076                }
11077                self.write_space();
11078                self.write_keyword("SORTKEY");
11079                self.write_space();
11080                if let Some(style) = this {
11081                    self.write_keyword(style);
11082                } else if !expressions.is_empty() {
11083                    self.write("(");
11084                    for (i, expr) in expressions.iter().enumerate() {
11085                        if i > 0 {
11086                            self.write(", ");
11087                        }
11088                        self.generate_expression(expr)?;
11089                    }
11090                    self.write(")");
11091                }
11092            }
11093            AlterTableAction::AlterDistStyle { style, distkey } => {
11094                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
11095                self.write_keyword("ALTER");
11096                self.write_space();
11097                self.write_keyword("DISTSTYLE");
11098                self.write_space();
11099                self.write_keyword(style);
11100                if let Some(col) = distkey {
11101                    self.write_space();
11102                    self.write_keyword("DISTKEY");
11103                    self.write_space();
11104                    self.generate_identifier(col)?;
11105                }
11106            }
11107            AlterTableAction::SetTableProperties { properties } => {
11108                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
11109                self.write_keyword("SET TABLE PROPERTIES");
11110                self.write(" (");
11111                for (i, (key, value)) in properties.iter().enumerate() {
11112                    if i > 0 {
11113                        self.write(", ");
11114                    }
11115                    self.generate_expression(key)?;
11116                    self.write(" = ");
11117                    self.generate_expression(value)?;
11118                }
11119                self.write(")");
11120            }
11121            AlterTableAction::SetLocation { location } => {
11122                // Redshift: SET LOCATION 's3://bucket/folder/'
11123                self.write_keyword("SET LOCATION");
11124                self.write_space();
11125                self.write("'");
11126                self.write(location);
11127                self.write("'");
11128            }
11129            AlterTableAction::SetFileFormat { format } => {
11130                // Redshift: SET FILE FORMAT AVRO
11131                self.write_keyword("SET FILE FORMAT");
11132                self.write_space();
11133                self.write_keyword(format);
11134            }
11135            AlterTableAction::ReplacePartition { partition, source } => {
11136                // ClickHouse: REPLACE PARTITION expr FROM source
11137                self.write_keyword("REPLACE PARTITION");
11138                self.write_space();
11139                self.generate_expression(partition)?;
11140                if let Some(src) = source {
11141                    self.write_space();
11142                    self.write_keyword("FROM");
11143                    self.write_space();
11144                    self.generate_expression(src)?;
11145                }
11146            }
11147            AlterTableAction::Raw { sql } => {
11148                self.write(sql);
11149            }
11150        }
11151        Ok(())
11152    }
11153
11154    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
11155        match action {
11156            AlterColumnAction::SetDataType {
11157                data_type,
11158                using,
11159                collate,
11160            } => {
11161                use crate::dialects::DialectType;
11162                // Dialect-specific type change syntax:
11163                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
11164                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
11165                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
11166                let is_no_prefix = matches!(
11167                    self.config.dialect,
11168                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
11169                );
11170                let is_type_only = matches!(
11171                    self.config.dialect,
11172                    Some(DialectType::Redshift)
11173                        | Some(DialectType::Spark)
11174                        | Some(DialectType::Databricks)
11175                );
11176                if is_type_only {
11177                    self.write_keyword("TYPE");
11178                    self.write_space();
11179                } else if !is_no_prefix {
11180                    self.write_keyword("SET DATA TYPE");
11181                    self.write_space();
11182                }
11183                self.generate_data_type(data_type)?;
11184                if let Some(ref collation) = collate {
11185                    self.write_space();
11186                    self.write_keyword("COLLATE");
11187                    self.write_space();
11188                    self.write(collation);
11189                }
11190                if let Some(ref using_expr) = using {
11191                    self.write_space();
11192                    self.write_keyword("USING");
11193                    self.write_space();
11194                    self.generate_expression(using_expr)?;
11195                }
11196            }
11197            AlterColumnAction::SetDefault(expr) => {
11198                self.write_keyword("SET DEFAULT");
11199                self.write_space();
11200                self.generate_expression(expr)?;
11201            }
11202            AlterColumnAction::DropDefault => {
11203                self.write_keyword("DROP DEFAULT");
11204            }
11205            AlterColumnAction::SetNotNull => {
11206                self.write_keyword("SET NOT NULL");
11207            }
11208            AlterColumnAction::DropNotNull => {
11209                self.write_keyword("DROP NOT NULL");
11210            }
11211            AlterColumnAction::Comment(comment) => {
11212                self.write_keyword("COMMENT");
11213                self.write_space();
11214                self.generate_string_literal(comment)?;
11215            }
11216            AlterColumnAction::SetVisible => {
11217                self.write_keyword("SET VISIBLE");
11218            }
11219            AlterColumnAction::SetInvisible => {
11220                self.write_keyword("SET INVISIBLE");
11221            }
11222        }
11223        Ok(())
11224    }
11225
11226    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
11227        self.write_keyword("CREATE");
11228
11229        if ci.unique {
11230            self.write_space();
11231            self.write_keyword("UNIQUE");
11232        }
11233
11234        // TSQL CLUSTERED/NONCLUSTERED modifier
11235        if let Some(ref clustered) = ci.clustered {
11236            self.write_space();
11237            self.write_keyword(clustered);
11238        }
11239
11240        self.write_space();
11241        self.write_keyword("INDEX");
11242
11243        // PostgreSQL CONCURRENTLY modifier
11244        if ci.concurrently {
11245            self.write_space();
11246            self.write_keyword("CONCURRENTLY");
11247        }
11248
11249        if ci.if_not_exists {
11250            self.write_space();
11251            self.write_keyword("IF NOT EXISTS");
11252        }
11253
11254        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
11255        if !ci.name.name.is_empty() {
11256            self.write_space();
11257            self.generate_identifier(&ci.name)?;
11258        }
11259        self.write_space();
11260        self.write_keyword("ON");
11261        // Hive uses ON TABLE
11262        if matches!(self.config.dialect, Some(DialectType::Hive)) {
11263            self.write_space();
11264            self.write_keyword("TABLE");
11265        }
11266        self.write_space();
11267        self.generate_table(&ci.table)?;
11268
11269        // Column list (optional for COLUMNSTORE indexes)
11270        // Standard SQL convention: ON t(a) without space before paren
11271        if !ci.columns.is_empty() || ci.using.is_some() {
11272            let space_before_paren = false;
11273
11274            if let Some(ref using) = ci.using {
11275                self.write_space();
11276                self.write_keyword("USING");
11277                self.write_space();
11278                self.write(using);
11279                if space_before_paren {
11280                    self.write(" (");
11281                } else {
11282                    self.write("(");
11283                }
11284            } else {
11285                if space_before_paren {
11286                    self.write(" (");
11287                } else {
11288                    self.write("(");
11289                }
11290            }
11291            for (i, col) in ci.columns.iter().enumerate() {
11292                if i > 0 {
11293                    self.write(", ");
11294                }
11295                self.generate_identifier(&col.column)?;
11296                if let Some(ref opclass) = col.opclass {
11297                    self.write_space();
11298                    self.write(opclass);
11299                }
11300                if col.desc {
11301                    self.write_space();
11302                    self.write_keyword("DESC");
11303                } else if col.asc {
11304                    self.write_space();
11305                    self.write_keyword("ASC");
11306                }
11307                if let Some(nulls_first) = col.nulls_first {
11308                    self.write_space();
11309                    self.write_keyword("NULLS");
11310                    self.write_space();
11311                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
11312                }
11313            }
11314            self.write(")");
11315        }
11316
11317        // PostgreSQL INCLUDE (col1, col2) clause
11318        if !ci.include_columns.is_empty() {
11319            self.write_space();
11320            self.write_keyword("INCLUDE");
11321            self.write(" (");
11322            for (i, col) in ci.include_columns.iter().enumerate() {
11323                if i > 0 {
11324                    self.write(", ");
11325                }
11326                self.generate_identifier(col)?;
11327            }
11328            self.write(")");
11329        }
11330
11331        // TSQL: WITH (option=value, ...) clause
11332        if !ci.with_options.is_empty() {
11333            self.write_space();
11334            self.write_keyword("WITH");
11335            self.write(" (");
11336            for (i, (key, value)) in ci.with_options.iter().enumerate() {
11337                if i > 0 {
11338                    self.write(", ");
11339                }
11340                self.write(key);
11341                self.write("=");
11342                self.write(value);
11343            }
11344            self.write(")");
11345        }
11346
11347        // PostgreSQL WHERE clause for partial indexes
11348        if let Some(ref where_clause) = ci.where_clause {
11349            self.write_space();
11350            self.write_keyword("WHERE");
11351            self.write_space();
11352            self.generate_expression(where_clause)?;
11353        }
11354
11355        // TSQL: ON filegroup or partition scheme clause
11356        if let Some(ref on_fg) = ci.on_filegroup {
11357            self.write_space();
11358            self.write_keyword("ON");
11359            self.write_space();
11360            self.write(on_fg);
11361        }
11362
11363        Ok(())
11364    }
11365
11366    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
11367        self.write_keyword("DROP INDEX");
11368
11369        if di.concurrently {
11370            self.write_space();
11371            self.write_keyword("CONCURRENTLY");
11372        }
11373
11374        if di.if_exists {
11375            self.write_space();
11376            self.write_keyword("IF EXISTS");
11377        }
11378
11379        self.write_space();
11380        self.generate_table(&di.name)?;
11381
11382        if let Some(ref table) = di.table {
11383            self.write_space();
11384            self.write_keyword("ON");
11385            self.write_space();
11386            self.generate_table(table)?;
11387        }
11388
11389        Ok(())
11390    }
11391
11392    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
11393        self.write_keyword("CREATE");
11394
11395        // MySQL: ALGORITHM=...
11396        if let Some(ref algorithm) = cv.algorithm {
11397            self.write_space();
11398            self.write_keyword("ALGORITHM");
11399            self.write("=");
11400            self.write_keyword(algorithm);
11401        }
11402
11403        // MySQL: DEFINER=...
11404        if let Some(ref definer) = cv.definer {
11405            self.write_space();
11406            self.write_keyword("DEFINER");
11407            self.write("=");
11408            self.write(definer);
11409        }
11410
11411        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
11412        if cv.security_sql_style && !cv.security_after_name {
11413            if let Some(ref security) = cv.security {
11414                self.write_space();
11415                self.write_keyword("SQL SECURITY");
11416                self.write_space();
11417                match security {
11418                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11419                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11420                    FunctionSecurity::None => self.write_keyword("NONE"),
11421                }
11422            }
11423        }
11424
11425        if cv.or_alter {
11426            self.write_space();
11427            self.write_keyword("OR ALTER");
11428        } else if cv.or_replace {
11429            self.write_space();
11430            self.write_keyword("OR REPLACE");
11431        }
11432
11433        if cv.temporary {
11434            self.write_space();
11435            self.write_keyword("TEMPORARY");
11436        }
11437
11438        if cv.materialized {
11439            self.write_space();
11440            self.write_keyword("MATERIALIZED");
11441        }
11442
11443        // Snowflake: SECURE VIEW
11444        if cv.secure {
11445            self.write_space();
11446            self.write_keyword("SECURE");
11447        }
11448
11449        self.write_space();
11450        self.write_keyword("VIEW");
11451
11452        if cv.if_not_exists {
11453            self.write_space();
11454            self.write_keyword("IF NOT EXISTS");
11455        }
11456
11457        self.write_space();
11458        self.generate_table(&cv.name)?;
11459
11460        // ClickHouse: ON CLUSTER clause
11461        if let Some(ref on_cluster) = cv.on_cluster {
11462            self.write_space();
11463            self.generate_on_cluster(on_cluster)?;
11464        }
11465
11466        // ClickHouse: TO destination_table
11467        if let Some(ref to_table) = cv.to_table {
11468            self.write_space();
11469            self.write_keyword("TO");
11470            self.write_space();
11471            self.generate_table(to_table)?;
11472        }
11473
11474        // For regular VIEW: columns come before COPY GRANTS
11475        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
11476        if !cv.materialized {
11477            // Regular VIEW: columns first
11478            if let Some(ref schema) = cv.schema {
11479                self.write(" (");
11480                for (i, expr) in schema.expressions.iter().enumerate() {
11481                    if i > 0 {
11482                        self.write(", ");
11483                    }
11484                    self.generate_expression(expr)?;
11485                }
11486                self.write(")");
11487            } else if !cv.columns.is_empty() {
11488                self.write(" (");
11489                for (i, col) in cv.columns.iter().enumerate() {
11490                    if i > 0 {
11491                        self.write(", ");
11492                    }
11493                    self.generate_identifier(&col.name)?;
11494                    // BigQuery: OPTIONS (key=value, ...) on view column
11495                    if !col.options.is_empty() {
11496                        self.write_space();
11497                        self.generate_options_clause(&col.options)?;
11498                    }
11499                    if let Some(ref comment) = col.comment {
11500                        self.write_space();
11501                        self.write_keyword("COMMENT");
11502                        self.write_space();
11503                        self.generate_string_literal(comment)?;
11504                    }
11505                }
11506                self.write(")");
11507            }
11508
11509            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
11510            // Also handles SQL SECURITY after view name (security_after_name)
11511            if !cv.security_sql_style || cv.security_after_name {
11512                if let Some(ref security) = cv.security {
11513                    self.write_space();
11514                    if cv.security_sql_style {
11515                        self.write_keyword("SQL SECURITY");
11516                    } else {
11517                        self.write_keyword("SECURITY");
11518                    }
11519                    self.write_space();
11520                    match security {
11521                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11522                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11523                        FunctionSecurity::None => self.write_keyword("NONE"),
11524                    }
11525                }
11526            }
11527
11528            // Snowflake: COPY GRANTS
11529            if cv.copy_grants {
11530                self.write_space();
11531                self.write_keyword("COPY GRANTS");
11532            }
11533        } else {
11534            // MATERIALIZED VIEW: COPY GRANTS first
11535            if cv.copy_grants {
11536                self.write_space();
11537                self.write_keyword("COPY GRANTS");
11538            }
11539
11540            // Doris: If we have a schema (typed columns), generate that instead
11541            if let Some(ref schema) = cv.schema {
11542                self.write(" (");
11543                for (i, expr) in schema.expressions.iter().enumerate() {
11544                    if i > 0 {
11545                        self.write(", ");
11546                    }
11547                    self.generate_expression(expr)?;
11548                }
11549                self.write(")");
11550            } else if !cv.columns.is_empty() {
11551                // Then columns (simple column names without types)
11552                self.write(" (");
11553                for (i, col) in cv.columns.iter().enumerate() {
11554                    if i > 0 {
11555                        self.write(", ");
11556                    }
11557                    self.generate_identifier(&col.name)?;
11558                    // BigQuery: OPTIONS (key=value, ...) on view column
11559                    if !col.options.is_empty() {
11560                        self.write_space();
11561                        self.generate_options_clause(&col.options)?;
11562                    }
11563                    if let Some(ref comment) = col.comment {
11564                        self.write_space();
11565                        self.write_keyword("COMMENT");
11566                        self.write_space();
11567                        self.generate_string_literal(comment)?;
11568                    }
11569                }
11570                self.write(")");
11571            }
11572
11573            // Doris: KEY (columns) for materialized views
11574            if let Some(ref unique_key) = cv.unique_key {
11575                self.write_space();
11576                self.write_keyword("KEY");
11577                self.write(" (");
11578                for (i, expr) in unique_key.expressions.iter().enumerate() {
11579                    if i > 0 {
11580                        self.write(", ");
11581                    }
11582                    self.generate_expression(expr)?;
11583                }
11584                self.write(")");
11585            }
11586        }
11587
11588        if let Some(ref row_access_policy) = cv.row_access_policy {
11589            self.write_space();
11590            self.write_keyword("WITH");
11591            self.write_space();
11592            self.write(row_access_policy);
11593        }
11594
11595        // Snowflake: COMMENT = 'text'
11596        if let Some(ref comment) = cv.comment {
11597            self.write_space();
11598            self.write_keyword("COMMENT");
11599            self.write("=");
11600            self.generate_string_literal(comment)?;
11601        }
11602
11603        // Snowflake: TAG (name='value', ...)
11604        if !cv.tags.is_empty() {
11605            self.write_space();
11606            self.write_keyword("TAG");
11607            self.write(" (");
11608            for (i, (name, value)) in cv.tags.iter().enumerate() {
11609                if i > 0 {
11610                    self.write(", ");
11611                }
11612                self.write(name);
11613                self.write("='");
11614                self.write(value);
11615                self.write("'");
11616            }
11617            self.write(")");
11618        }
11619
11620        // BigQuery: OPTIONS (key=value, ...)
11621        if !cv.options.is_empty() {
11622            self.write_space();
11623            self.generate_options_clause(&cv.options)?;
11624        }
11625
11626        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
11627        if let Some(ref build) = cv.build {
11628            self.write_space();
11629            self.write_keyword("BUILD");
11630            self.write_space();
11631            self.write_keyword(build);
11632        }
11633
11634        // Doris: REFRESH clause for materialized views
11635        if let Some(ref refresh) = cv.refresh {
11636            self.write_space();
11637            self.generate_refresh_trigger_property(refresh)?;
11638        }
11639
11640        // Redshift: AUTO REFRESH YES|NO for materialized views
11641        if let Some(auto_refresh) = cv.auto_refresh {
11642            self.write_space();
11643            self.write_keyword("AUTO REFRESH");
11644            self.write_space();
11645            if auto_refresh {
11646                self.write_keyword("YES");
11647            } else {
11648                self.write_keyword("NO");
11649            }
11650        }
11651
11652        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
11653        for prop in &cv.table_properties {
11654            self.write_space();
11655            self.generate_expression(prop)?;
11656        }
11657
11658        // ClickHouse: POPULATE / EMPTY before AS
11659        if let Some(ref population) = cv.clickhouse_population {
11660            self.write_space();
11661            self.write_keyword(population);
11662        }
11663
11664        // Only output AS clause if there's a real query (not just NULL placeholder)
11665        if !matches!(&cv.query, Expression::Null(_)) {
11666            self.write_space();
11667            self.write_keyword("AS");
11668            self.write_space();
11669
11670            // Teradata: LOCKING clause (between AS and query)
11671            if let Some(ref mode) = cv.locking_mode {
11672                self.write_keyword("LOCKING");
11673                self.write_space();
11674                self.write_keyword(mode);
11675                if let Some(ref access) = cv.locking_access {
11676                    self.write_space();
11677                    self.write_keyword("FOR");
11678                    self.write_space();
11679                    self.write_keyword(access);
11680                }
11681                self.write_space();
11682            }
11683
11684            if cv.query_parenthesized {
11685                self.write("(");
11686            }
11687            self.generate_expression(&cv.query)?;
11688            if cv.query_parenthesized {
11689                self.write(")");
11690            }
11691        }
11692
11693        // Redshift: WITH NO SCHEMA BINDING (after query)
11694        if cv.no_schema_binding {
11695            self.write_space();
11696            self.write_keyword("WITH NO SCHEMA BINDING");
11697        }
11698
11699        Ok(())
11700    }
11701
11702    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
11703        self.write_keyword("DROP");
11704
11705        if dv.materialized {
11706            self.write_space();
11707            self.write_keyword("MATERIALIZED");
11708        }
11709
11710        self.write_space();
11711        self.write_keyword("VIEW");
11712
11713        if dv.if_exists {
11714            self.write_space();
11715            self.write_keyword("IF EXISTS");
11716        }
11717
11718        self.write_space();
11719        self.generate_table(&dv.name)?;
11720
11721        Ok(())
11722    }
11723
11724    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
11725        match tr.target {
11726            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
11727            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
11728        }
11729        if tr.if_exists {
11730            self.write_space();
11731            self.write_keyword("IF EXISTS");
11732        }
11733        self.write_space();
11734        self.generate_table(&tr.table)?;
11735
11736        // ClickHouse: ON CLUSTER clause
11737        if let Some(ref on_cluster) = tr.on_cluster {
11738            self.write_space();
11739            self.generate_on_cluster(on_cluster)?;
11740        }
11741
11742        // Check if first table has a * (multi-table with star)
11743        if !tr.extra_tables.is_empty() {
11744            // Check if the first entry matches the main table (star case)
11745            let skip_first = if let Some(first) = tr.extra_tables.first() {
11746                first.table.name == tr.table.name && first.star
11747            } else {
11748                false
11749            };
11750
11751            // PostgreSQL normalizes away the * suffix (it's the default behavior)
11752            let strip_star = matches!(
11753                self.config.dialect,
11754                Some(crate::dialects::DialectType::PostgreSQL)
11755                    | Some(crate::dialects::DialectType::Redshift)
11756            );
11757            if skip_first && !strip_star {
11758                self.write("*");
11759            }
11760
11761            // Generate additional tables
11762            for (i, entry) in tr.extra_tables.iter().enumerate() {
11763                if i == 0 && skip_first {
11764                    continue; // Already handled the star for first table
11765                }
11766                self.write(", ");
11767                self.generate_table(&entry.table)?;
11768                if entry.star && !strip_star {
11769                    self.write("*");
11770                }
11771            }
11772        }
11773
11774        // RESTART/CONTINUE IDENTITY
11775        if let Some(identity) = &tr.identity {
11776            self.write_space();
11777            match identity {
11778                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
11779                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
11780            }
11781        }
11782
11783        if tr.cascade {
11784            self.write_space();
11785            self.write_keyword("CASCADE");
11786        }
11787
11788        if tr.restrict {
11789            self.write_space();
11790            self.write_keyword("RESTRICT");
11791        }
11792
11793        // Output Hive PARTITION clause
11794        if let Some(ref partition) = tr.partition {
11795            self.write_space();
11796            self.generate_expression(partition)?;
11797        }
11798
11799        Ok(())
11800    }
11801
11802    fn generate_use(&mut self, u: &Use) -> Result<()> {
11803        // Teradata uses "DATABASE <name>" instead of "USE <name>"
11804        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
11805            self.write_keyword("DATABASE");
11806            self.write_space();
11807            self.generate_identifier(&u.this)?;
11808            return Ok(());
11809        }
11810
11811        self.write_keyword("USE");
11812
11813        if let Some(kind) = &u.kind {
11814            self.write_space();
11815            match kind {
11816                UseKind::Database => self.write_keyword("DATABASE"),
11817                UseKind::Schema => self.write_keyword("SCHEMA"),
11818                UseKind::Role => self.write_keyword("ROLE"),
11819                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
11820                UseKind::Catalog => self.write_keyword("CATALOG"),
11821                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
11822            }
11823        }
11824
11825        self.write_space();
11826        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
11827        // without quoting, since these are keywords not identifiers
11828        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
11829            self.write(&u.this.name);
11830        } else {
11831            self.generate_identifier(&u.this)?;
11832        }
11833        Ok(())
11834    }
11835
11836    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
11837        self.write_keyword("CACHE");
11838        if c.lazy {
11839            self.write_space();
11840            self.write_keyword("LAZY");
11841        }
11842        self.write_space();
11843        self.write_keyword("TABLE");
11844        self.write_space();
11845        self.generate_identifier(&c.table)?;
11846
11847        // OPTIONS clause
11848        if !c.options.is_empty() {
11849            self.write_space();
11850            self.write_keyword("OPTIONS");
11851            self.write("(");
11852            for (i, (key, value)) in c.options.iter().enumerate() {
11853                if i > 0 {
11854                    self.write(", ");
11855                }
11856                self.generate_expression(key)?;
11857                self.write(" = ");
11858                self.generate_expression(value)?;
11859            }
11860            self.write(")");
11861        }
11862
11863        // AS query
11864        if let Some(query) = &c.query {
11865            self.write_space();
11866            self.write_keyword("AS");
11867            self.write_space();
11868            self.generate_expression(query)?;
11869        }
11870
11871        Ok(())
11872    }
11873
11874    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
11875        self.write_keyword("UNCACHE TABLE");
11876        if u.if_exists {
11877            self.write_space();
11878            self.write_keyword("IF EXISTS");
11879        }
11880        self.write_space();
11881        self.generate_identifier(&u.table)?;
11882        Ok(())
11883    }
11884
11885    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
11886        self.write_keyword("LOAD DATA");
11887        if l.local {
11888            self.write_space();
11889            self.write_keyword("LOCAL");
11890        }
11891        self.write_space();
11892        self.write_keyword("INPATH");
11893        self.write_space();
11894        self.write("'");
11895        self.write(&l.inpath);
11896        self.write("'");
11897
11898        if l.overwrite {
11899            self.write_space();
11900            self.write_keyword("OVERWRITE");
11901        }
11902
11903        self.write_space();
11904        self.write_keyword("INTO TABLE");
11905        self.write_space();
11906        self.generate_expression(&l.table)?;
11907
11908        // PARTITION clause
11909        if !l.partition.is_empty() {
11910            self.write_space();
11911            self.write_keyword("PARTITION");
11912            self.write("(");
11913            for (i, (col, val)) in l.partition.iter().enumerate() {
11914                if i > 0 {
11915                    self.write(", ");
11916                }
11917                self.generate_identifier(col)?;
11918                self.write(" = ");
11919                self.generate_expression(val)?;
11920            }
11921            self.write(")");
11922        }
11923
11924        // INPUTFORMAT clause
11925        if let Some(fmt) = &l.input_format {
11926            self.write_space();
11927            self.write_keyword("INPUTFORMAT");
11928            self.write_space();
11929            self.write("'");
11930            self.write(fmt);
11931            self.write("'");
11932        }
11933
11934        // SERDE clause
11935        if let Some(serde) = &l.serde {
11936            self.write_space();
11937            self.write_keyword("SERDE");
11938            self.write_space();
11939            self.write("'");
11940            self.write(serde);
11941            self.write("'");
11942        }
11943
11944        Ok(())
11945    }
11946
11947    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
11948        self.write_keyword("PRAGMA");
11949        self.write_space();
11950
11951        // Schema prefix if present
11952        if let Some(schema) = &p.schema {
11953            self.generate_identifier(schema)?;
11954            self.write(".");
11955        }
11956
11957        // Pragma name
11958        self.generate_identifier(&p.name)?;
11959
11960        // Value assignment or function call
11961        if p.use_assignment_syntax {
11962            self.write(" = ");
11963            if let Some(value) = &p.value {
11964                self.generate_expression(value)?;
11965            } else if let Some(arg) = p.args.first() {
11966                self.generate_expression(arg)?;
11967            }
11968        } else if !p.args.is_empty() {
11969            self.write("(");
11970            for (i, arg) in p.args.iter().enumerate() {
11971                if i > 0 {
11972                    self.write(", ");
11973                }
11974                self.generate_expression(arg)?;
11975            }
11976            self.write(")");
11977        }
11978
11979        Ok(())
11980    }
11981
11982    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
11983        self.write_keyword("GRANT");
11984        self.write_space();
11985
11986        // Privileges (with optional column lists)
11987        for (i, privilege) in g.privileges.iter().enumerate() {
11988            if i > 0 {
11989                self.write(", ");
11990            }
11991            self.write_keyword(&privilege.name);
11992            // Output column list if present: SELECT(col1, col2)
11993            if !privilege.columns.is_empty() {
11994                self.write("(");
11995                for (j, col) in privilege.columns.iter().enumerate() {
11996                    if j > 0 {
11997                        self.write(", ");
11998                    }
11999                    self.write(col);
12000                }
12001                self.write(")");
12002            }
12003        }
12004
12005        self.write_space();
12006        self.write_keyword("ON");
12007        self.write_space();
12008
12009        // Object kind (TABLE, SCHEMA, etc.)
12010        if let Some(kind) = &g.kind {
12011            self.write_keyword(kind);
12012            self.write_space();
12013        }
12014
12015        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
12016        {
12017            use crate::dialects::DialectType;
12018            let should_upper = matches!(
12019                self.config.dialect,
12020                Some(DialectType::PostgreSQL)
12021                    | Some(DialectType::CockroachDB)
12022                    | Some(DialectType::Materialize)
12023                    | Some(DialectType::RisingWave)
12024            ) && (g.kind.as_deref() == Some("FUNCTION")
12025                || g.kind.as_deref() == Some("PROCEDURE"));
12026            if should_upper {
12027                use crate::expressions::Identifier;
12028                let upper_id = Identifier {
12029                    name: g.securable.name.to_ascii_uppercase(),
12030                    quoted: g.securable.quoted,
12031                    ..g.securable.clone()
12032                };
12033                self.generate_identifier(&upper_id)?;
12034            } else {
12035                self.generate_identifier(&g.securable)?;
12036            }
12037        }
12038
12039        // Function parameter types (if present)
12040        if !g.function_params.is_empty() {
12041            self.write("(");
12042            for (i, param) in g.function_params.iter().enumerate() {
12043                if i > 0 {
12044                    self.write(", ");
12045                }
12046                self.write(param);
12047            }
12048            self.write(")");
12049        }
12050
12051        self.write_space();
12052        self.write_keyword("TO");
12053        self.write_space();
12054
12055        // Principals
12056        for (i, principal) in g.principals.iter().enumerate() {
12057            if i > 0 {
12058                self.write(", ");
12059            }
12060            if principal.is_role {
12061                self.write_keyword("ROLE");
12062                self.write_space();
12063            } else if principal.is_group {
12064                self.write_keyword("GROUP");
12065                self.write_space();
12066            } else if principal.is_share {
12067                self.write_keyword("SHARE");
12068                self.write_space();
12069            }
12070            self.generate_identifier(&principal.name)?;
12071        }
12072
12073        // WITH GRANT OPTION
12074        if g.grant_option {
12075            self.write_space();
12076            self.write_keyword("WITH GRANT OPTION");
12077        }
12078
12079        // TSQL: AS principal
12080        if let Some(ref principal) = g.as_principal {
12081            self.write_space();
12082            self.write_keyword("AS");
12083            self.write_space();
12084            self.generate_identifier(principal)?;
12085        }
12086
12087        Ok(())
12088    }
12089
12090    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
12091        self.write_keyword("REVOKE");
12092        self.write_space();
12093
12094        // GRANT OPTION FOR
12095        if r.grant_option {
12096            self.write_keyword("GRANT OPTION FOR");
12097            self.write_space();
12098        }
12099
12100        // Privileges (with optional column lists)
12101        for (i, privilege) in r.privileges.iter().enumerate() {
12102            if i > 0 {
12103                self.write(", ");
12104            }
12105            self.write_keyword(&privilege.name);
12106            // Output column list if present: SELECT(col1, col2)
12107            if !privilege.columns.is_empty() {
12108                self.write("(");
12109                for (j, col) in privilege.columns.iter().enumerate() {
12110                    if j > 0 {
12111                        self.write(", ");
12112                    }
12113                    self.write(col);
12114                }
12115                self.write(")");
12116            }
12117        }
12118
12119        self.write_space();
12120        self.write_keyword("ON");
12121        self.write_space();
12122
12123        // Object kind
12124        if let Some(kind) = &r.kind {
12125            self.write_keyword(kind);
12126            self.write_space();
12127        }
12128
12129        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
12130        {
12131            use crate::dialects::DialectType;
12132            let should_upper = matches!(
12133                self.config.dialect,
12134                Some(DialectType::PostgreSQL)
12135                    | Some(DialectType::CockroachDB)
12136                    | Some(DialectType::Materialize)
12137                    | Some(DialectType::RisingWave)
12138            ) && (r.kind.as_deref() == Some("FUNCTION")
12139                || r.kind.as_deref() == Some("PROCEDURE"));
12140            if should_upper {
12141                use crate::expressions::Identifier;
12142                let upper_id = Identifier {
12143                    name: r.securable.name.to_ascii_uppercase(),
12144                    quoted: r.securable.quoted,
12145                    ..r.securable.clone()
12146                };
12147                self.generate_identifier(&upper_id)?;
12148            } else {
12149                self.generate_identifier(&r.securable)?;
12150            }
12151        }
12152
12153        // Function parameter types (if present)
12154        if !r.function_params.is_empty() {
12155            self.write("(");
12156            for (i, param) in r.function_params.iter().enumerate() {
12157                if i > 0 {
12158                    self.write(", ");
12159                }
12160                self.write(param);
12161            }
12162            self.write(")");
12163        }
12164
12165        self.write_space();
12166        self.write_keyword("FROM");
12167        self.write_space();
12168
12169        // Principals
12170        for (i, principal) in r.principals.iter().enumerate() {
12171            if i > 0 {
12172                self.write(", ");
12173            }
12174            if principal.is_role {
12175                self.write_keyword("ROLE");
12176                self.write_space();
12177            } else if principal.is_group {
12178                self.write_keyword("GROUP");
12179                self.write_space();
12180            } else if principal.is_share {
12181                self.write_keyword("SHARE");
12182                self.write_space();
12183            }
12184            self.generate_identifier(&principal.name)?;
12185        }
12186
12187        // CASCADE or RESTRICT
12188        if r.cascade {
12189            self.write_space();
12190            self.write_keyword("CASCADE");
12191        } else if r.restrict {
12192            self.write_space();
12193            self.write_keyword("RESTRICT");
12194        }
12195
12196        Ok(())
12197    }
12198
12199    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
12200        self.write_keyword("COMMENT");
12201
12202        // IF EXISTS
12203        if c.exists {
12204            self.write_space();
12205            self.write_keyword("IF EXISTS");
12206        }
12207
12208        self.write_space();
12209        self.write_keyword("ON");
12210
12211        // MATERIALIZED
12212        if c.materialized {
12213            self.write_space();
12214            self.write_keyword("MATERIALIZED");
12215        }
12216
12217        self.write_space();
12218        self.write_keyword(&c.kind);
12219        self.write_space();
12220
12221        // Object name
12222        self.generate_expression(&c.this)?;
12223
12224        self.write_space();
12225        self.write_keyword("IS");
12226        self.write_space();
12227
12228        // Comment expression
12229        self.generate_expression(&c.expression)?;
12230
12231        Ok(())
12232    }
12233
12234    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
12235        self.write_keyword("SET");
12236
12237        for (i, item) in s.items.iter().enumerate() {
12238            if i > 0 {
12239                self.write(",");
12240            }
12241            self.write_space();
12242
12243            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
12244            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
12245            if let Some(ref kind) = item.kind {
12246                // For VARIABLE kind, only output the keyword for dialects that require it
12247                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
12248                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
12249                if has_variable_kind {
12250                    if matches!(
12251                        self.config.dialect,
12252                        Some(DialectType::Spark | DialectType::Databricks | DialectType::DuckDB)
12253                    ) {
12254                        self.write_keyword("VARIABLE");
12255                        self.write_space();
12256                    }
12257                } else {
12258                    self.write_keyword(kind);
12259                    self.write_space();
12260                }
12261            }
12262
12263            // Check for special SET forms by name
12264            let name_str = match &item.name {
12265                Expression::Identifier(id) => Some(id.name.as_str()),
12266                _ => None,
12267            };
12268
12269            let is_transaction = name_str == Some("TRANSACTION");
12270            let is_character_set = name_str == Some("CHARACTER SET");
12271            let is_names = name_str == Some("NAMES");
12272            let is_collate = name_str == Some("COLLATE");
12273            let is_identity_insert = name_str == Some("IDENTITY_INSERT");
12274            let is_value_only =
12275                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
12276
12277            if is_transaction {
12278                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
12279                self.write_keyword("TRANSACTION");
12280                if let Expression::Identifier(id) = &item.value {
12281                    if !id.name.is_empty() {
12282                        self.write_space();
12283                        self.write(&id.name);
12284                    }
12285                }
12286            } else if is_character_set {
12287                // Output: SET CHARACTER SET <charset>
12288                self.write_keyword("CHARACTER SET");
12289                self.write_space();
12290                self.generate_set_value(&item.value)?;
12291            } else if is_names {
12292                // Output: SET NAMES <charset>
12293                self.write_keyword("NAMES");
12294                self.write_space();
12295                self.generate_set_value(&item.value)?;
12296            } else if is_collate {
12297                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
12298                self.write_keyword("COLLATE");
12299                self.write_space();
12300                self.generate_set_value(&item.value)?;
12301            } else if is_identity_insert {
12302                // T-SQL: SET IDENTITY_INSERT <table> ON|OFF
12303                self.write_keyword("IDENTITY_INSERT");
12304                self.write_space();
12305                self.generate_identity_insert_value(&item.value)?;
12306            } else if has_variable_kind {
12307                // Output: SET [VARIABLE] <name> = <value>
12308                // VARIABLE keyword already written above if dialect requires it
12309                if let Some(ns) = name_str {
12310                    self.write(ns);
12311                } else {
12312                    self.generate_expression(&item.name)?;
12313                }
12314                self.write(" = ");
12315                self.generate_set_value(&item.value)?;
12316            } else if is_value_only {
12317                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
12318                self.generate_expression(&item.name)?;
12319            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
12320                // SET key value without = (TSQL style)
12321                self.generate_expression(&item.name)?;
12322                self.write_space();
12323                self.generate_set_value(&item.value)?;
12324            } else {
12325                // Standard: variable = value
12326                // SET item names should not be quoted (they are config parameter names, not column refs)
12327                match &item.name {
12328                    Expression::Identifier(id) => {
12329                        self.write(&id.name);
12330                    }
12331                    _ => {
12332                        self.generate_expression(&item.name)?;
12333                    }
12334                }
12335                self.write(" = ");
12336                self.generate_set_value(&item.value)?;
12337            }
12338        }
12339
12340        Ok(())
12341    }
12342
12343    fn generate_identity_insert_value(&mut self, value: &Expression) -> Result<()> {
12344        if let Expression::Tuple(tuple) = value {
12345            if tuple.expressions.len() == 2 {
12346                self.generate_expression(&tuple.expressions[0])?;
12347                self.write_space();
12348                self.generate_set_value(&tuple.expressions[1])?;
12349                return Ok(());
12350            }
12351        }
12352
12353        self.generate_set_value(value)
12354    }
12355
12356    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
12357    /// directly to avoid reserved keyword quoting.
12358    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
12359        if let Expression::Identifier(id) = value {
12360            match id.name.as_str() {
12361                "DEFAULT" | "ON" | "OFF" => {
12362                    self.write_keyword(&id.name);
12363                    return Ok(());
12364                }
12365                _ => {}
12366            }
12367        }
12368        self.generate_expression(value)
12369    }
12370
12371    // ==================== Phase 4: Additional DDL Generation ====================
12372
12373    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
12374        self.write_keyword("ALTER");
12375        // MySQL modifiers before VIEW
12376        if let Some(ref algorithm) = av.algorithm {
12377            self.write_space();
12378            self.write_keyword("ALGORITHM");
12379            self.write(" = ");
12380            self.write_keyword(algorithm);
12381        }
12382        if let Some(ref definer) = av.definer {
12383            self.write_space();
12384            self.write_keyword("DEFINER");
12385            self.write(" = ");
12386            self.write(definer);
12387        }
12388        if let Some(ref sql_security) = av.sql_security {
12389            self.write_space();
12390            self.write_keyword("SQL SECURITY");
12391            self.write(" = ");
12392            self.write_keyword(sql_security);
12393        }
12394        self.write_space();
12395        self.write_keyword("VIEW");
12396        self.write_space();
12397        self.generate_table(&av.name)?;
12398
12399        // Hive: Column aliases with optional COMMENT
12400        if !av.columns.is_empty() {
12401            self.write(" (");
12402            for (i, col) in av.columns.iter().enumerate() {
12403                if i > 0 {
12404                    self.write(", ");
12405                }
12406                self.generate_identifier(&col.name)?;
12407                if let Some(ref comment) = col.comment {
12408                    self.write_space();
12409                    self.write_keyword("COMMENT");
12410                    self.write(" ");
12411                    self.generate_string_literal(comment)?;
12412                }
12413            }
12414            self.write(")");
12415        }
12416
12417        // TSQL: WITH option before actions
12418        if let Some(ref opt) = av.with_option {
12419            self.write_space();
12420            self.write_keyword("WITH");
12421            self.write_space();
12422            self.write_keyword(opt);
12423        }
12424
12425        for action in &av.actions {
12426            self.write_space();
12427            match action {
12428                AlterViewAction::Rename(new_name) => {
12429                    self.write_keyword("RENAME TO");
12430                    self.write_space();
12431                    self.generate_table(new_name)?;
12432                }
12433                AlterViewAction::OwnerTo(owner) => {
12434                    self.write_keyword("OWNER TO");
12435                    self.write_space();
12436                    self.generate_identifier(owner)?;
12437                }
12438                AlterViewAction::SetSchema(schema) => {
12439                    self.write_keyword("SET SCHEMA");
12440                    self.write_space();
12441                    self.generate_identifier(schema)?;
12442                }
12443                AlterViewAction::SetAuthorization(auth) => {
12444                    self.write_keyword("SET AUTHORIZATION");
12445                    self.write_space();
12446                    self.write(auth);
12447                }
12448                AlterViewAction::AlterColumn { name, action } => {
12449                    self.write_keyword("ALTER COLUMN");
12450                    self.write_space();
12451                    self.generate_identifier(name)?;
12452                    self.write_space();
12453                    self.generate_alter_column_action(action)?;
12454                }
12455                AlterViewAction::AsSelect(query) => {
12456                    self.write_keyword("AS");
12457                    self.write_space();
12458                    self.generate_expression(query)?;
12459                }
12460                AlterViewAction::SetTblproperties(props) => {
12461                    self.write_keyword("SET TBLPROPERTIES");
12462                    self.write(" (");
12463                    for (i, (key, value)) in props.iter().enumerate() {
12464                        if i > 0 {
12465                            self.write(", ");
12466                        }
12467                        self.generate_string_literal(key)?;
12468                        self.write("=");
12469                        self.generate_string_literal(value)?;
12470                    }
12471                    self.write(")");
12472                }
12473                AlterViewAction::UnsetTblproperties(keys) => {
12474                    self.write_keyword("UNSET TBLPROPERTIES");
12475                    self.write(" (");
12476                    for (i, key) in keys.iter().enumerate() {
12477                        if i > 0 {
12478                            self.write(", ");
12479                        }
12480                        self.generate_string_literal(key)?;
12481                    }
12482                    self.write(")");
12483                }
12484            }
12485        }
12486
12487        Ok(())
12488    }
12489
12490    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
12491        self.write_keyword("ALTER INDEX");
12492        self.write_space();
12493        self.generate_identifier(&ai.name)?;
12494
12495        if let Some(table) = &ai.table {
12496            self.write_space();
12497            self.write_keyword("ON");
12498            self.write_space();
12499            self.generate_table(table)?;
12500        }
12501
12502        for action in &ai.actions {
12503            self.write_space();
12504            match action {
12505                AlterIndexAction::Rename(new_name) => {
12506                    self.write_keyword("RENAME TO");
12507                    self.write_space();
12508                    self.generate_identifier(new_name)?;
12509                }
12510                AlterIndexAction::SetTablespace(tablespace) => {
12511                    self.write_keyword("SET TABLESPACE");
12512                    self.write_space();
12513                    self.generate_identifier(tablespace)?;
12514                }
12515                AlterIndexAction::Visible(visible) => {
12516                    if *visible {
12517                        self.write_keyword("VISIBLE");
12518                    } else {
12519                        self.write_keyword("INVISIBLE");
12520                    }
12521                }
12522            }
12523        }
12524
12525        Ok(())
12526    }
12527
12528    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
12529        // Output leading comments
12530        for comment in &cs.leading_comments {
12531            self.write_formatted_comment(comment);
12532            self.write_space();
12533        }
12534
12535        // Athena: CREATE SCHEMA uses Hive engine (backticks)
12536        let saved_athena_hive_context = self.athena_hive_context;
12537        if matches!(
12538            self.config.dialect,
12539            Some(crate::dialects::DialectType::Athena)
12540        ) {
12541            self.athena_hive_context = true;
12542        }
12543
12544        self.write_keyword("CREATE SCHEMA");
12545
12546        if cs.if_not_exists {
12547            self.write_space();
12548            self.write_keyword("IF NOT EXISTS");
12549        }
12550
12551        self.write_space();
12552        for (i, part) in cs.name.iter().enumerate() {
12553            if i > 0 {
12554                self.write(".");
12555            }
12556            self.generate_identifier(part)?;
12557        }
12558
12559        if let Some(ref clone_parts) = cs.clone_from {
12560            self.write_keyword(" CLONE ");
12561            for (i, part) in clone_parts.iter().enumerate() {
12562                if i > 0 {
12563                    self.write(".");
12564                }
12565                self.generate_identifier(part)?;
12566            }
12567        }
12568
12569        if let Some(ref at_clause) = cs.at_clause {
12570            self.write_space();
12571            self.generate_expression(at_clause)?;
12572        }
12573
12574        if let Some(auth) = &cs.authorization {
12575            self.write_space();
12576            self.write_keyword("AUTHORIZATION");
12577            self.write_space();
12578            self.generate_identifier(auth)?;
12579        }
12580
12581        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
12582        // Separate WITH properties from other properties
12583        let with_properties: Vec<_> = cs
12584            .properties
12585            .iter()
12586            .filter(|p| matches!(p, Expression::Property(_)))
12587            .collect();
12588        let other_properties: Vec<_> = cs
12589            .properties
12590            .iter()
12591            .filter(|p| !matches!(p, Expression::Property(_)))
12592            .collect();
12593
12594        // Generate WITH (props) if we have Property expressions
12595        if !with_properties.is_empty() {
12596            self.write_space();
12597            self.write_keyword("WITH");
12598            self.write(" (");
12599            for (i, prop) in with_properties.iter().enumerate() {
12600                if i > 0 {
12601                    self.write(", ");
12602                }
12603                self.generate_expression(prop)?;
12604            }
12605            self.write(")");
12606        }
12607
12608        // Generate other properties (like DEFAULT COLLATE)
12609        for prop in other_properties {
12610            self.write_space();
12611            self.generate_expression(prop)?;
12612        }
12613
12614        // Restore Athena Hive context
12615        self.athena_hive_context = saved_athena_hive_context;
12616
12617        Ok(())
12618    }
12619
12620    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
12621        self.write_keyword("DROP SCHEMA");
12622
12623        if ds.if_exists {
12624            self.write_space();
12625            self.write_keyword("IF EXISTS");
12626        }
12627
12628        self.write_space();
12629        self.generate_identifier(&ds.name)?;
12630
12631        if ds.cascade {
12632            self.write_space();
12633            self.write_keyword("CASCADE");
12634        }
12635
12636        Ok(())
12637    }
12638
12639    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
12640        self.write_keyword("DROP NAMESPACE");
12641
12642        if dn.if_exists {
12643            self.write_space();
12644            self.write_keyword("IF EXISTS");
12645        }
12646
12647        self.write_space();
12648        self.generate_identifier(&dn.name)?;
12649
12650        if dn.cascade {
12651            self.write_space();
12652            self.write_keyword("CASCADE");
12653        }
12654
12655        Ok(())
12656    }
12657
12658    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
12659        self.write_keyword("CREATE DATABASE");
12660
12661        if cd.if_not_exists {
12662            self.write_space();
12663            self.write_keyword("IF NOT EXISTS");
12664        }
12665
12666        self.write_space();
12667        self.generate_identifier(&cd.name)?;
12668
12669        if let Some(ref clone_src) = cd.clone_from {
12670            self.write_keyword(" CLONE ");
12671            self.generate_identifier(clone_src)?;
12672        }
12673
12674        // AT/BEFORE clause for time travel (Snowflake)
12675        if let Some(ref at_clause) = cd.at_clause {
12676            self.write_space();
12677            self.generate_expression(at_clause)?;
12678        }
12679
12680        for option in &cd.options {
12681            self.write_space();
12682            match option {
12683                DatabaseOption::CharacterSet(charset) => {
12684                    self.write_keyword("CHARACTER SET");
12685                    self.write(" = ");
12686                    self.write(&format!("'{}'", charset));
12687                }
12688                DatabaseOption::Collate(collate) => {
12689                    self.write_keyword("COLLATE");
12690                    self.write(" = ");
12691                    self.write(&format!("'{}'", collate));
12692                }
12693                DatabaseOption::Owner(owner) => {
12694                    self.write_keyword("OWNER");
12695                    self.write(" = ");
12696                    self.generate_identifier(owner)?;
12697                }
12698                DatabaseOption::Template(template) => {
12699                    self.write_keyword("TEMPLATE");
12700                    self.write(" = ");
12701                    self.generate_identifier(template)?;
12702                }
12703                DatabaseOption::Encoding(encoding) => {
12704                    self.write_keyword("ENCODING");
12705                    self.write(" = ");
12706                    self.write(&format!("'{}'", encoding));
12707                }
12708                DatabaseOption::Location(location) => {
12709                    self.write_keyword("LOCATION");
12710                    self.write(" = ");
12711                    self.write(&format!("'{}'", location));
12712                }
12713            }
12714        }
12715
12716        Ok(())
12717    }
12718
12719    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
12720        self.write_keyword("DROP DATABASE");
12721
12722        if dd.if_exists {
12723            self.write_space();
12724            self.write_keyword("IF EXISTS");
12725        }
12726
12727        self.write_space();
12728        self.generate_identifier(&dd.name)?;
12729
12730        if dd.sync {
12731            self.write_space();
12732            self.write_keyword("SYNC");
12733        }
12734
12735        Ok(())
12736    }
12737
12738    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
12739        self.write_keyword("CREATE");
12740
12741        if cf.or_alter {
12742            self.write_space();
12743            self.write_keyword("OR ALTER");
12744        } else if cf.or_replace {
12745            self.write_space();
12746            self.write_keyword("OR REPLACE");
12747        }
12748
12749        if cf.temporary {
12750            self.write_space();
12751            self.write_keyword("TEMPORARY");
12752        }
12753
12754        self.write_space();
12755        if cf.is_table_function {
12756            self.write_keyword("TABLE FUNCTION");
12757        } else {
12758            self.write_keyword("FUNCTION");
12759        }
12760
12761        if cf.if_not_exists {
12762            self.write_space();
12763            self.write_keyword("IF NOT EXISTS");
12764        }
12765
12766        self.write_space();
12767        self.generate_table(&cf.name)?;
12768        if cf.has_parens {
12769            let func_multiline = self.config.pretty
12770                && matches!(
12771                    self.config.dialect,
12772                    Some(crate::dialects::DialectType::TSQL)
12773                        | Some(crate::dialects::DialectType::Fabric)
12774                )
12775                && !cf.parameters.is_empty();
12776            if func_multiline {
12777                self.write("(\n");
12778                self.indent_level += 2;
12779                self.write_indent();
12780                self.generate_function_parameters(&cf.parameters)?;
12781                self.write("\n");
12782                self.indent_level -= 2;
12783                self.write(")");
12784            } else {
12785                self.write("(");
12786                self.generate_function_parameters(&cf.parameters)?;
12787                self.write(")");
12788            }
12789        }
12790
12791        // Output RETURNS clause (always comes first after parameters)
12792        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
12793        let use_multiline = self.config.pretty
12794            && matches!(
12795                self.config.dialect,
12796                Some(crate::dialects::DialectType::BigQuery)
12797                    | Some(crate::dialects::DialectType::TSQL)
12798                    | Some(crate::dialects::DialectType::Fabric)
12799            );
12800
12801        if cf.language_first {
12802            // LANGUAGE first, then SQL data access, then RETURNS
12803            if let Some(lang) = &cf.language {
12804                if use_multiline {
12805                    self.write_newline();
12806                } else {
12807                    self.write_space();
12808                }
12809                self.write_keyword("LANGUAGE");
12810                self.write_space();
12811                self.write(lang);
12812            }
12813
12814            // SQL data access comes after LANGUAGE in this case
12815            if let Some(sql_data) = &cf.sql_data_access {
12816                self.write_space();
12817                match sql_data {
12818                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12819                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12820                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12821                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12822                }
12823            }
12824
12825            if let Some(ref rtb) = cf.returns_table_body {
12826                if use_multiline {
12827                    self.write_newline();
12828                } else {
12829                    self.write_space();
12830                }
12831                self.write_keyword("RETURNS");
12832                self.write_space();
12833                self.write(rtb);
12834            } else if let Some(return_type) = &cf.return_type {
12835                if use_multiline {
12836                    self.write_newline();
12837                } else {
12838                    self.write_space();
12839                }
12840                self.write_keyword("RETURNS");
12841                self.write_space();
12842                self.generate_function_return_type(return_type)?;
12843            }
12844        } else {
12845            // RETURNS first (default)
12846            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
12847            let is_duckdb = matches!(
12848                self.config.dialect,
12849                Some(crate::dialects::DialectType::DuckDB)
12850            );
12851            if let Some(ref rtb) = cf.returns_table_body {
12852                if !(is_duckdb && rtb.is_empty()) {
12853                    if use_multiline {
12854                        self.write_newline();
12855                    } else {
12856                        self.write_space();
12857                    }
12858                    self.write_keyword("RETURNS");
12859                    self.write_space();
12860                    self.write(rtb);
12861                }
12862            } else if let Some(return_type) = &cf.return_type {
12863                // DuckDB: skip all RETURNS (DuckDB macros don't use RETURNS clause)
12864                if !is_duckdb {
12865                    let is_table_return = matches!(return_type, crate::expressions::DataType::Custom { ref name } if name.eq_ignore_ascii_case("TABLE"));
12866                    if use_multiline {
12867                        self.write_newline();
12868                    } else {
12869                        self.write_space();
12870                    }
12871                    self.write_keyword("RETURNS");
12872                    self.write_space();
12873                    if is_table_return {
12874                        self.write_keyword("TABLE");
12875                    } else {
12876                        self.generate_function_return_type(return_type)?;
12877                    }
12878                }
12879            }
12880        }
12881
12882        // If we have property_order, use it to output properties in original order
12883        if !cf.property_order.is_empty() {
12884            // For BigQuery, OPTIONS must come before AS - reorder if needed
12885            let is_bigquery = matches!(
12886                self.config.dialect,
12887                Some(crate::dialects::DialectType::BigQuery)
12888            );
12889            let is_postgres = matches!(
12890                self.config.dialect,
12891                Some(crate::dialects::DialectType::PostgreSQL)
12892            );
12893            let property_order = if is_bigquery {
12894                // Move Options before As if both are present
12895                let mut reordered = Vec::new();
12896                let mut has_as = false;
12897                let mut has_options = false;
12898                for prop in &cf.property_order {
12899                    match prop {
12900                        FunctionPropertyKind::As => has_as = true,
12901                        FunctionPropertyKind::Options => has_options = true,
12902                        _ => {}
12903                    }
12904                }
12905                if has_as && has_options {
12906                    // Output all props except As and Options, then Options, then As
12907                    for prop in &cf.property_order {
12908                        if *prop != FunctionPropertyKind::As
12909                            && *prop != FunctionPropertyKind::Options
12910                        {
12911                            reordered.push(*prop);
12912                        }
12913                    }
12914                    reordered.push(FunctionPropertyKind::Options);
12915                    reordered.push(FunctionPropertyKind::As);
12916                    reordered
12917                } else {
12918                    cf.property_order.clone()
12919                }
12920            } else if is_postgres
12921                && cf.property_order.contains(&FunctionPropertyKind::As)
12922                && cf.property_order.contains(&FunctionPropertyKind::NullInput)
12923            {
12924                let mut reordered: Vec<_> = cf
12925                    .property_order
12926                    .iter()
12927                    .copied()
12928                    .filter(|prop| *prop != FunctionPropertyKind::As)
12929                    .collect();
12930                reordered.push(FunctionPropertyKind::As);
12931                reordered
12932            } else {
12933                cf.property_order.clone()
12934            };
12935
12936            for prop in &property_order {
12937                match prop {
12938                    FunctionPropertyKind::Set => {
12939                        self.generate_function_set_options(cf)?;
12940                    }
12941                    FunctionPropertyKind::As => {
12942                        self.generate_function_body(cf)?;
12943                    }
12944                    FunctionPropertyKind::Using => {
12945                        self.generate_function_using_resources(cf)?;
12946                    }
12947                    FunctionPropertyKind::Language => {
12948                        if !cf.language_first {
12949                            // Only output here if not already output above
12950                            if let Some(lang) = &cf.language {
12951                                // Only BigQuery uses multiline formatting
12952                                let use_multiline = self.config.pretty
12953                                    && matches!(
12954                                        self.config.dialect,
12955                                        Some(crate::dialects::DialectType::BigQuery)
12956                                    );
12957                                if use_multiline {
12958                                    self.write_newline();
12959                                } else {
12960                                    self.write_space();
12961                                }
12962                                self.write_keyword("LANGUAGE");
12963                                self.write_space();
12964                                self.write(lang);
12965                            }
12966                        }
12967                    }
12968                    FunctionPropertyKind::Determinism => {
12969                        self.generate_function_determinism(cf)?;
12970                    }
12971                    FunctionPropertyKind::NullInput => {
12972                        self.generate_function_null_input(cf)?;
12973                    }
12974                    FunctionPropertyKind::Security => {
12975                        self.generate_function_security(cf)?;
12976                    }
12977                    FunctionPropertyKind::SqlDataAccess => {
12978                        if !cf.language_first {
12979                            // Only output here if not already output above
12980                            self.generate_function_sql_data_access(cf)?;
12981                        }
12982                    }
12983                    FunctionPropertyKind::Options => {
12984                        if !cf.options.is_empty() {
12985                            self.write_space();
12986                            self.generate_options_clause(&cf.options)?;
12987                        }
12988                    }
12989                    FunctionPropertyKind::Environment => {
12990                        if !cf.environment.is_empty() {
12991                            self.write_space();
12992                            self.generate_environment_clause(&cf.environment)?;
12993                        }
12994                    }
12995                    FunctionPropertyKind::Handler => {
12996                        if let Some(ref h) = cf.handler {
12997                            self.write_space();
12998                            self.write_keyword("HANDLER");
12999                            if cf.handler_uses_eq {
13000                                self.write(" = ");
13001                            } else {
13002                                self.write_space();
13003                            }
13004                            self.write("'");
13005                            self.write(h);
13006                            self.write("'");
13007                        }
13008                    }
13009                    FunctionPropertyKind::RuntimeVersion => {
13010                        if let Some(ref runtime_version) = cf.runtime_version {
13011                            self.write_space();
13012                            self.write_keyword("RUNTIME_VERSION");
13013                            self.write("='");
13014                            self.write(runtime_version);
13015                            self.write("'");
13016                        }
13017                    }
13018                    FunctionPropertyKind::Packages => {
13019                        if let Some(ref packages) = cf.packages {
13020                            self.write_space();
13021                            self.write_keyword("PACKAGES");
13022                            self.write("=(");
13023                            for (i, package) in packages.iter().enumerate() {
13024                                if i > 0 {
13025                                    self.write(", ");
13026                                }
13027                                self.write("'");
13028                                self.write(package);
13029                                self.write("'");
13030                            }
13031                            self.write(")");
13032                        }
13033                    }
13034                    FunctionPropertyKind::ParameterStyle => {
13035                        if let Some(ref ps) = cf.parameter_style {
13036                            self.write_space();
13037                            self.write_keyword("PARAMETER STYLE");
13038                            self.write_space();
13039                            self.write_keyword(ps);
13040                        }
13041                    }
13042                }
13043            }
13044
13045            // Output OPTIONS if not tracked in property_order (legacy)
13046            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
13047            {
13048                self.write_space();
13049                self.generate_options_clause(&cf.options)?;
13050            }
13051
13052            // Output ENVIRONMENT if not tracked in property_order (legacy)
13053            if !cf.environment.is_empty()
13054                && !cf
13055                    .property_order
13056                    .contains(&FunctionPropertyKind::Environment)
13057            {
13058                self.write_space();
13059                self.generate_environment_clause(&cf.environment)?;
13060            }
13061        } else {
13062            // Legacy behavior when property_order is empty
13063            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
13064            if matches!(
13065                self.config.dialect,
13066                Some(crate::dialects::DialectType::BigQuery)
13067            ) {
13068                self.generate_function_determinism(cf)?;
13069            }
13070
13071            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
13072            let use_multiline = self.config.pretty
13073                && matches!(
13074                    self.config.dialect,
13075                    Some(crate::dialects::DialectType::BigQuery)
13076                );
13077
13078            if !cf.language_first {
13079                if let Some(lang) = &cf.language {
13080                    if use_multiline {
13081                        self.write_newline();
13082                    } else {
13083                        self.write_space();
13084                    }
13085                    self.write_keyword("LANGUAGE");
13086                    self.write_space();
13087                    self.write(lang);
13088                }
13089
13090                // SQL data access characteristic comes after LANGUAGE
13091                self.generate_function_sql_data_access(cf)?;
13092            }
13093
13094            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
13095            if !matches!(
13096                self.config.dialect,
13097                Some(crate::dialects::DialectType::BigQuery)
13098            ) {
13099                self.generate_function_determinism(cf)?;
13100            }
13101
13102            self.generate_function_null_input(cf)?;
13103            self.generate_function_security(cf)?;
13104            self.generate_function_set_options(cf)?;
13105
13106            // BigQuery: OPTIONS (key=value, ...) - comes before AS
13107            if !cf.options.is_empty() {
13108                self.write_space();
13109                self.generate_options_clause(&cf.options)?;
13110            }
13111
13112            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
13113            if !cf.environment.is_empty() {
13114                self.write_space();
13115                self.generate_environment_clause(&cf.environment)?;
13116            }
13117
13118            if let Some(ref h) = cf.handler {
13119                self.write_space();
13120                self.write_keyword("HANDLER");
13121                if cf.handler_uses_eq {
13122                    self.write(" = ");
13123                } else {
13124                    self.write_space();
13125                }
13126                self.write("'");
13127                self.write(h);
13128                self.write("'");
13129            }
13130
13131            if let Some(ref runtime_version) = cf.runtime_version {
13132                self.write_space();
13133                self.write_keyword("RUNTIME_VERSION");
13134                self.write("='");
13135                self.write(runtime_version);
13136                self.write("'");
13137            }
13138
13139            if let Some(ref packages) = cf.packages {
13140                self.write_space();
13141                self.write_keyword("PACKAGES");
13142                self.write("=(");
13143                for (i, package) in packages.iter().enumerate() {
13144                    if i > 0 {
13145                        self.write(", ");
13146                    }
13147                    self.write("'");
13148                    self.write(package);
13149                    self.write("'");
13150                }
13151                self.write(")");
13152            }
13153
13154            self.generate_function_body(cf)?;
13155            self.generate_function_using_resources(cf)?;
13156        }
13157
13158        Ok(())
13159    }
13160
13161    fn generate_function_return_type(&mut self, return_type: &DataType) -> Result<()> {
13162        if matches!(
13163            self.config.dialect,
13164            Some(crate::dialects::DialectType::PostgreSQL)
13165        ) {
13166            if let DataType::Custom { name } = return_type {
13167                if name.eq_ignore_ascii_case("integer") {
13168                    self.write_keyword("INT");
13169                    return Ok(());
13170                }
13171            }
13172        }
13173
13174        self.generate_data_type(return_type)
13175    }
13176
13177    /// Generate SET options for CREATE FUNCTION
13178    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
13179        for opt in &cf.set_options {
13180            self.write_space();
13181            self.write_keyword("SET");
13182            self.write_space();
13183            self.write(&opt.name);
13184            match &opt.value {
13185                FunctionSetValue::Value { value, use_to } => {
13186                    if *use_to {
13187                        self.write(" TO ");
13188                    } else {
13189                        self.write(" = ");
13190                    }
13191                    self.write(value);
13192                }
13193                FunctionSetValue::FromCurrent => {
13194                    self.write_space();
13195                    self.write_keyword("FROM CURRENT");
13196                }
13197            }
13198        }
13199        Ok(())
13200    }
13201
13202    fn generate_function_using_resources(&mut self, cf: &CreateFunction) -> Result<()> {
13203        if cf.using_resources.is_empty() {
13204            return Ok(());
13205        }
13206
13207        self.write_space();
13208        self.write_keyword("USING");
13209        for resource in &cf.using_resources {
13210            self.write_space();
13211            self.write_keyword(&resource.kind);
13212            self.write_space();
13213            self.generate_string_literal(&resource.uri)?;
13214        }
13215        Ok(())
13216    }
13217
13218    /// Generate function body (AS clause)
13219    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
13220        if let Some(body) = &cf.body {
13221            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
13222            self.write_space();
13223            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
13224            let use_multiline = self.config.pretty
13225                && matches!(
13226                    self.config.dialect,
13227                    Some(crate::dialects::DialectType::BigQuery)
13228                );
13229            match body {
13230                FunctionBody::Block(block) => {
13231                    self.write_keyword("AS");
13232                    if matches!(
13233                        self.config.dialect,
13234                        Some(crate::dialects::DialectType::TSQL)
13235                    ) {
13236                        self.write(" BEGIN ");
13237                        self.write(block);
13238                        self.write(" END");
13239                    } else if matches!(
13240                        self.config.dialect,
13241                        Some(crate::dialects::DialectType::PostgreSQL)
13242                    ) {
13243                        self.write(" $$");
13244                        self.write(block);
13245                        self.write("$$");
13246                    } else {
13247                        // Escape content for single-quoted output
13248                        let escaped = self.escape_block_for_single_quote(block);
13249                        // In BigQuery pretty mode, body content goes on new line
13250                        if use_multiline {
13251                            self.write_newline();
13252                        } else {
13253                            self.write(" ");
13254                        }
13255                        self.write("'");
13256                        self.write(&escaped);
13257                        self.write("'");
13258                    }
13259                }
13260                FunctionBody::StringLiteral(s) => {
13261                    self.write_keyword("AS");
13262                    // In BigQuery pretty mode, body content goes on new line
13263                    if use_multiline {
13264                        self.write_newline();
13265                    } else {
13266                        self.write(" ");
13267                    }
13268                    self.write("'");
13269                    self.write(s);
13270                    self.write("'");
13271                }
13272                FunctionBody::Expression(expr) => {
13273                    self.write_keyword("AS");
13274                    self.write_space();
13275                    self.generate_expression(expr)?;
13276                }
13277                FunctionBody::External(name) => {
13278                    self.write_keyword("EXTERNAL NAME");
13279                    self.write(" '");
13280                    self.write(name);
13281                    self.write("'");
13282                }
13283                FunctionBody::Return(expr) => {
13284                    if matches!(
13285                        self.config.dialect,
13286                        Some(crate::dialects::DialectType::DuckDB)
13287                    ) {
13288                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
13289                        self.write_keyword("AS");
13290                        self.write_space();
13291                        // Check both returns_table_body marker and return_type = Custom "TABLE"
13292                        let is_table_return = cf.returns_table_body.is_some()
13293                            || matches!(&cf.return_type, Some(crate::expressions::DataType::Custom { ref name }) if name.eq_ignore_ascii_case("TABLE"));
13294                        if is_table_return {
13295                            self.write_keyword("TABLE");
13296                            self.write_space();
13297                        }
13298                        self.generate_expression(expr)?;
13299                    } else {
13300                        if self.config.create_function_return_as {
13301                            self.write_keyword("AS");
13302                            // TSQL pretty: newline between AS and RETURN
13303                            if self.config.pretty
13304                                && matches!(
13305                                    self.config.dialect,
13306                                    Some(crate::dialects::DialectType::TSQL)
13307                                        | Some(crate::dialects::DialectType::Fabric)
13308                                )
13309                            {
13310                                self.write_newline();
13311                            } else {
13312                                self.write_space();
13313                            }
13314                        }
13315                        self.write_keyword("RETURN");
13316                        self.write_space();
13317                        self.generate_expression(expr)?;
13318                    }
13319                }
13320                FunctionBody::Statements(stmts) => {
13321                    self.write_keyword("AS");
13322                    self.write(" BEGIN ");
13323                    for (i, stmt) in stmts.iter().enumerate() {
13324                        if i > 0 {
13325                            self.write(" ");
13326                        }
13327                        self.generate_expression(stmt)?;
13328                        self.write(";");
13329                    }
13330                    self.write(" END");
13331                }
13332                FunctionBody::RawBlock(text) => {
13333                    self.write_newline();
13334                    self.write(text);
13335                }
13336                FunctionBody::DollarQuoted { content, tag } => {
13337                    self.write_keyword("AS");
13338                    self.write(" ");
13339                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13340                    let supports_dollar_quoting = matches!(
13341                        self.config.dialect,
13342                        Some(crate::dialects::DialectType::PostgreSQL)
13343                            | Some(crate::dialects::DialectType::Databricks)
13344                            | Some(crate::dialects::DialectType::Redshift)
13345                            | Some(crate::dialects::DialectType::DuckDB)
13346                    );
13347                    if supports_dollar_quoting {
13348                        // Output in dollar-quoted format
13349                        self.write("$");
13350                        if let Some(t) = tag {
13351                            self.write(t);
13352                        }
13353                        self.write("$");
13354                        self.write(content);
13355                        self.write("$");
13356                        if let Some(t) = tag {
13357                            self.write(t);
13358                        }
13359                        self.write("$");
13360                    } else {
13361                        // Convert to single-quoted string for other dialects
13362                        let escaped = self.escape_block_for_single_quote(content);
13363                        self.write("'");
13364                        self.write(&escaped);
13365                        self.write("'");
13366                    }
13367                }
13368            }
13369        }
13370        Ok(())
13371    }
13372
13373    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
13374    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
13375        if let Some(det) = cf.deterministic {
13376            self.write_space();
13377            if matches!(
13378                self.config.dialect,
13379                Some(crate::dialects::DialectType::BigQuery)
13380            ) {
13381                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
13382                if det {
13383                    self.write_keyword("DETERMINISTIC");
13384                } else {
13385                    self.write_keyword("NOT DETERMINISTIC");
13386                }
13387            } else {
13388                // PostgreSQL and others use IMMUTABLE/VOLATILE
13389                if det {
13390                    self.write_keyword("IMMUTABLE");
13391                } else {
13392                    self.write_keyword("VOLATILE");
13393                }
13394            }
13395        }
13396        Ok(())
13397    }
13398
13399    /// Generate null input handling clause
13400    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
13401        if let Some(returns_null) = cf.returns_null_on_null_input {
13402            self.write_space();
13403            if returns_null {
13404                if cf.strict {
13405                    self.write_keyword("STRICT");
13406                } else {
13407                    self.write_keyword("RETURNS NULL ON NULL INPUT");
13408                }
13409            } else {
13410                self.write_keyword("CALLED ON NULL INPUT");
13411            }
13412        }
13413        Ok(())
13414    }
13415
13416    /// Generate security clause
13417    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
13418        if let Some(security) = &cf.security {
13419            self.write_space();
13420            // MySQL uses SQL SECURITY prefix
13421            if matches!(
13422                self.config.dialect,
13423                Some(crate::dialects::DialectType::MySQL)
13424            ) {
13425                self.write_keyword("SQL SECURITY");
13426            } else {
13427                self.write_keyword("SECURITY");
13428            }
13429            self.write_space();
13430            match security {
13431                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13432                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13433                FunctionSecurity::None => self.write_keyword("NONE"),
13434            }
13435        }
13436        Ok(())
13437    }
13438
13439    /// Generate SQL data access clause
13440    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
13441        if let Some(sql_data) = &cf.sql_data_access {
13442            self.write_space();
13443            match sql_data {
13444                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
13445                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
13446                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
13447                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
13448            }
13449        }
13450        Ok(())
13451    }
13452
13453    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
13454        for (i, param) in params.iter().enumerate() {
13455            if i > 0 {
13456                self.write(", ");
13457            }
13458
13459            if let Some(mode) = &param.mode {
13460                if let Some(text) = &param.mode_text {
13461                    self.write(text);
13462                } else {
13463                    match mode {
13464                        ParameterMode::In => self.write_keyword("IN"),
13465                        ParameterMode::Out => self.write_keyword("OUT"),
13466                        ParameterMode::InOut => self.write_keyword("INOUT"),
13467                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
13468                    }
13469                }
13470                self.write_space();
13471            }
13472
13473            if let Some(name) = &param.name {
13474                self.generate_identifier(name)?;
13475                // Skip space and type for empty Custom types (e.g., DuckDB macros)
13476                let skip_type =
13477                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
13478                if !skip_type {
13479                    self.write_space();
13480                    self.generate_data_type(&param.data_type)?;
13481                }
13482            } else {
13483                self.generate_data_type(&param.data_type)?;
13484            }
13485
13486            if let Some(default) = &param.default {
13487                if self.config.parameter_default_equals {
13488                    self.write(" = ");
13489                } else {
13490                    self.write(" DEFAULT ");
13491                }
13492                self.generate_expression(default)?;
13493            }
13494        }
13495
13496        Ok(())
13497    }
13498
13499    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
13500        self.write_keyword("DROP FUNCTION");
13501
13502        if df.if_exists {
13503            self.write_space();
13504            self.write_keyword("IF EXISTS");
13505        }
13506
13507        self.write_space();
13508        self.generate_table(&df.name)?;
13509
13510        if let Some(params) = &df.parameters {
13511            self.write(" (");
13512            for (i, dt) in params.iter().enumerate() {
13513                if i > 0 {
13514                    self.write(", ");
13515                }
13516                self.generate_data_type(dt)?;
13517            }
13518            self.write(")");
13519        }
13520
13521        if df.cascade {
13522            self.write_space();
13523            self.write_keyword("CASCADE");
13524        }
13525
13526        Ok(())
13527    }
13528
13529    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
13530        self.write_keyword("CREATE");
13531
13532        if cp.or_alter {
13533            self.write_space();
13534            self.write_keyword("OR ALTER");
13535        } else if cp.or_replace {
13536            self.write_space();
13537            self.write_keyword("OR REPLACE");
13538        }
13539
13540        self.write_space();
13541        if cp.use_proc_keyword {
13542            self.write_keyword("PROC");
13543        } else {
13544            self.write_keyword("PROCEDURE");
13545        }
13546
13547        if cp.if_not_exists {
13548            self.write_space();
13549            self.write_keyword("IF NOT EXISTS");
13550        }
13551
13552        self.write_space();
13553        self.generate_table(&cp.name)?;
13554        if cp.has_parens {
13555            self.write("(");
13556            self.generate_function_parameters(&cp.parameters)?;
13557            self.write(")");
13558        } else if !cp.parameters.is_empty() {
13559            // TSQL: unparenthesized parameters
13560            self.write_space();
13561            self.generate_function_parameters(&cp.parameters)?;
13562        }
13563
13564        // RETURNS clause (Snowflake)
13565        if let Some(return_type) = &cp.return_type {
13566            self.write_space();
13567            self.write_keyword("RETURNS");
13568            self.write_space();
13569            self.generate_data_type(return_type)?;
13570        }
13571
13572        // EXECUTE AS clause (Snowflake)
13573        if let Some(execute_as) = &cp.execute_as {
13574            self.write_space();
13575            self.write_keyword("EXECUTE AS");
13576            self.write_space();
13577            self.write_keyword(execute_as);
13578        }
13579
13580        if let Some(lang) = &cp.language {
13581            self.write_space();
13582            self.write_keyword("LANGUAGE");
13583            self.write_space();
13584            self.write(lang);
13585        }
13586
13587        if let Some(security) = &cp.security {
13588            self.write_space();
13589            self.write_keyword("SECURITY");
13590            self.write_space();
13591            match security {
13592                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13593                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13594                FunctionSecurity::None => self.write_keyword("NONE"),
13595            }
13596        }
13597
13598        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
13599        if !cp.with_options.is_empty() {
13600            self.write_space();
13601            self.write_keyword("WITH");
13602            self.write_space();
13603            for (i, opt) in cp.with_options.iter().enumerate() {
13604                if i > 0 {
13605                    self.write(", ");
13606                }
13607                self.write(opt);
13608            }
13609        }
13610
13611        if let Some(body) = &cp.body {
13612            self.write_space();
13613            match body {
13614                FunctionBody::Block(block) => {
13615                    self.write_keyword("AS");
13616                    if matches!(
13617                        self.config.dialect,
13618                        Some(crate::dialects::DialectType::TSQL)
13619                    ) {
13620                        self.write(" BEGIN ");
13621                        self.write(block);
13622                        self.write(" END");
13623                    } else if matches!(
13624                        self.config.dialect,
13625                        Some(crate::dialects::DialectType::PostgreSQL)
13626                    ) {
13627                        self.write(" $$");
13628                        self.write(block);
13629                        self.write("$$");
13630                    } else {
13631                        // Escape content for single-quoted output
13632                        let escaped = self.escape_block_for_single_quote(block);
13633                        self.write(" '");
13634                        self.write(&escaped);
13635                        self.write("'");
13636                    }
13637                }
13638                FunctionBody::StringLiteral(s) => {
13639                    self.write_keyword("AS");
13640                    self.write(" '");
13641                    self.write(s);
13642                    self.write("'");
13643                }
13644                FunctionBody::Expression(expr) => {
13645                    self.write_keyword("AS");
13646                    self.write_space();
13647                    self.generate_expression(expr)?;
13648                }
13649                FunctionBody::External(name) => {
13650                    self.write_keyword("EXTERNAL NAME");
13651                    self.write(" '");
13652                    self.write(name);
13653                    self.write("'");
13654                }
13655                FunctionBody::Return(expr) => {
13656                    self.write_keyword("RETURN");
13657                    self.write_space();
13658                    self.generate_expression(expr)?;
13659                }
13660                FunctionBody::Statements(stmts) => {
13661                    self.write_keyword("AS");
13662                    self.write(" BEGIN ");
13663                    for (i, stmt) in stmts.iter().enumerate() {
13664                        if i > 0 {
13665                            self.write(" ");
13666                        }
13667                        self.generate_expression(stmt)?;
13668                        self.write(";");
13669                    }
13670                    self.write(" END");
13671                }
13672                FunctionBody::RawBlock(text) => {
13673                    self.write_newline();
13674                    self.write(text);
13675                }
13676                FunctionBody::DollarQuoted { content, tag } => {
13677                    self.write_keyword("AS");
13678                    self.write(" ");
13679                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13680                    let supports_dollar_quoting = matches!(
13681                        self.config.dialect,
13682                        Some(crate::dialects::DialectType::PostgreSQL)
13683                            | Some(crate::dialects::DialectType::Databricks)
13684                            | Some(crate::dialects::DialectType::Redshift)
13685                            | Some(crate::dialects::DialectType::DuckDB)
13686                    );
13687                    if supports_dollar_quoting {
13688                        // Output in dollar-quoted format
13689                        self.write("$");
13690                        if let Some(t) = tag {
13691                            self.write(t);
13692                        }
13693                        self.write("$");
13694                        self.write(content);
13695                        self.write("$");
13696                        if let Some(t) = tag {
13697                            self.write(t);
13698                        }
13699                        self.write("$");
13700                    } else {
13701                        // Convert to single-quoted string for other dialects
13702                        let escaped = self.escape_block_for_single_quote(content);
13703                        self.write("'");
13704                        self.write(&escaped);
13705                        self.write("'");
13706                    }
13707                }
13708            }
13709        }
13710
13711        Ok(())
13712    }
13713
13714    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
13715        self.write_keyword("DROP PROCEDURE");
13716
13717        if dp.if_exists {
13718            self.write_space();
13719            self.write_keyword("IF EXISTS");
13720        }
13721
13722        self.write_space();
13723        self.generate_table(&dp.name)?;
13724
13725        if let Some(params) = &dp.parameters {
13726            self.write(" (");
13727            for (i, dt) in params.iter().enumerate() {
13728                if i > 0 {
13729                    self.write(", ");
13730                }
13731                self.generate_data_type(dt)?;
13732            }
13733            self.write(")");
13734        }
13735
13736        if dp.cascade {
13737            self.write_space();
13738            self.write_keyword("CASCADE");
13739        }
13740
13741        Ok(())
13742    }
13743
13744    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
13745        self.write_keyword("CREATE");
13746
13747        if cs.or_replace {
13748            self.write_space();
13749            self.write_keyword("OR REPLACE");
13750        }
13751
13752        if cs.temporary {
13753            self.write_space();
13754            self.write_keyword("TEMPORARY");
13755        }
13756
13757        self.write_space();
13758        self.write_keyword("SEQUENCE");
13759
13760        if cs.if_not_exists {
13761            self.write_space();
13762            self.write_keyword("IF NOT EXISTS");
13763        }
13764
13765        self.write_space();
13766        self.generate_table(&cs.name)?;
13767
13768        // Output AS <type> if present
13769        if let Some(as_type) = &cs.as_type {
13770            self.write_space();
13771            self.write_keyword("AS");
13772            self.write_space();
13773            self.generate_data_type(as_type)?;
13774        }
13775
13776        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
13777        if let Some(comment) = &cs.comment {
13778            self.write_space();
13779            self.write_keyword("COMMENT");
13780            self.write("=");
13781            self.generate_string_literal(comment)?;
13782        }
13783
13784        // If property_order is available, use it to preserve original order
13785        if !cs.property_order.is_empty() {
13786            for prop in &cs.property_order {
13787                match prop {
13788                    SeqPropKind::Start => {
13789                        if let Some(start) = cs.start {
13790                            self.write_space();
13791                            self.write_keyword("START WITH");
13792                            self.write(&format!(" {}", start));
13793                        }
13794                    }
13795                    SeqPropKind::Increment => {
13796                        if let Some(inc) = cs.increment {
13797                            self.write_space();
13798                            self.write_keyword("INCREMENT BY");
13799                            self.write(&format!(" {}", inc));
13800                        }
13801                    }
13802                    SeqPropKind::Minvalue => {
13803                        if let Some(min) = &cs.minvalue {
13804                            self.write_space();
13805                            match min {
13806                                SequenceBound::Value(v) => {
13807                                    self.write_keyword("MINVALUE");
13808                                    self.write(&format!(" {}", v));
13809                                }
13810                                SequenceBound::None => {
13811                                    self.write_keyword("NO MINVALUE");
13812                                }
13813                            }
13814                        }
13815                    }
13816                    SeqPropKind::Maxvalue => {
13817                        if let Some(max) = &cs.maxvalue {
13818                            self.write_space();
13819                            match max {
13820                                SequenceBound::Value(v) => {
13821                                    self.write_keyword("MAXVALUE");
13822                                    self.write(&format!(" {}", v));
13823                                }
13824                                SequenceBound::None => {
13825                                    self.write_keyword("NO MAXVALUE");
13826                                }
13827                            }
13828                        }
13829                    }
13830                    SeqPropKind::Cache => {
13831                        if let Some(cache) = cs.cache {
13832                            self.write_space();
13833                            self.write_keyword("CACHE");
13834                            self.write(&format!(" {}", cache));
13835                        }
13836                    }
13837                    SeqPropKind::NoCache => {
13838                        self.write_space();
13839                        self.write_keyword("NO CACHE");
13840                    }
13841                    SeqPropKind::NoCacheWord => {
13842                        self.write_space();
13843                        self.write_keyword("NOCACHE");
13844                    }
13845                    SeqPropKind::Cycle => {
13846                        self.write_space();
13847                        self.write_keyword("CYCLE");
13848                    }
13849                    SeqPropKind::NoCycle => {
13850                        self.write_space();
13851                        self.write_keyword("NO CYCLE");
13852                    }
13853                    SeqPropKind::NoCycleWord => {
13854                        self.write_space();
13855                        self.write_keyword("NOCYCLE");
13856                    }
13857                    SeqPropKind::OwnedBy => {
13858                        // Skip OWNED BY NONE (it's a no-op)
13859                        if !cs.owned_by_none {
13860                            if let Some(owned) = &cs.owned_by {
13861                                self.write_space();
13862                                self.write_keyword("OWNED BY");
13863                                self.write_space();
13864                                self.generate_table(owned)?;
13865                            }
13866                        }
13867                    }
13868                    SeqPropKind::Order => {
13869                        self.write_space();
13870                        self.write_keyword("ORDER");
13871                    }
13872                    SeqPropKind::NoOrder => {
13873                        self.write_space();
13874                        self.write_keyword("NOORDER");
13875                    }
13876                    SeqPropKind::Comment => {
13877                        // COMMENT is output above, before property_order iteration
13878                    }
13879                    SeqPropKind::Sharing => {
13880                        if let Some(val) = &cs.sharing {
13881                            self.write_space();
13882                            self.write(&format!("SHARING={}", val));
13883                        }
13884                    }
13885                    SeqPropKind::Keep => {
13886                        self.write_space();
13887                        self.write_keyword("KEEP");
13888                    }
13889                    SeqPropKind::NoKeep => {
13890                        self.write_space();
13891                        self.write_keyword("NOKEEP");
13892                    }
13893                    SeqPropKind::Scale => {
13894                        self.write_space();
13895                        self.write_keyword("SCALE");
13896                        if let Some(modifier) = &cs.scale_modifier {
13897                            if !modifier.is_empty() {
13898                                self.write_space();
13899                                self.write_keyword(modifier);
13900                            }
13901                        }
13902                    }
13903                    SeqPropKind::NoScale => {
13904                        self.write_space();
13905                        self.write_keyword("NOSCALE");
13906                    }
13907                    SeqPropKind::Shard => {
13908                        self.write_space();
13909                        self.write_keyword("SHARD");
13910                        if let Some(modifier) = &cs.shard_modifier {
13911                            if !modifier.is_empty() {
13912                                self.write_space();
13913                                self.write_keyword(modifier);
13914                            }
13915                        }
13916                    }
13917                    SeqPropKind::NoShard => {
13918                        self.write_space();
13919                        self.write_keyword("NOSHARD");
13920                    }
13921                    SeqPropKind::Session => {
13922                        self.write_space();
13923                        self.write_keyword("SESSION");
13924                    }
13925                    SeqPropKind::Global => {
13926                        self.write_space();
13927                        self.write_keyword("GLOBAL");
13928                    }
13929                    SeqPropKind::NoMinvalueWord => {
13930                        self.write_space();
13931                        self.write_keyword("NOMINVALUE");
13932                    }
13933                    SeqPropKind::NoMaxvalueWord => {
13934                        self.write_space();
13935                        self.write_keyword("NOMAXVALUE");
13936                    }
13937                }
13938            }
13939        } else {
13940            // Fallback: default order for backwards compatibility
13941            if let Some(inc) = cs.increment {
13942                self.write_space();
13943                self.write_keyword("INCREMENT BY");
13944                self.write(&format!(" {}", inc));
13945            }
13946
13947            if let Some(min) = &cs.minvalue {
13948                self.write_space();
13949                match min {
13950                    SequenceBound::Value(v) => {
13951                        self.write_keyword("MINVALUE");
13952                        self.write(&format!(" {}", v));
13953                    }
13954                    SequenceBound::None => {
13955                        self.write_keyword("NO MINVALUE");
13956                    }
13957                }
13958            }
13959
13960            if let Some(max) = &cs.maxvalue {
13961                self.write_space();
13962                match max {
13963                    SequenceBound::Value(v) => {
13964                        self.write_keyword("MAXVALUE");
13965                        self.write(&format!(" {}", v));
13966                    }
13967                    SequenceBound::None => {
13968                        self.write_keyword("NO MAXVALUE");
13969                    }
13970                }
13971            }
13972
13973            if let Some(start) = cs.start {
13974                self.write_space();
13975                self.write_keyword("START WITH");
13976                self.write(&format!(" {}", start));
13977            }
13978
13979            if let Some(cache) = cs.cache {
13980                self.write_space();
13981                self.write_keyword("CACHE");
13982                self.write(&format!(" {}", cache));
13983            }
13984
13985            if cs.cycle {
13986                self.write_space();
13987                self.write_keyword("CYCLE");
13988            }
13989
13990            if let Some(owned) = &cs.owned_by {
13991                self.write_space();
13992                self.write_keyword("OWNED BY");
13993                self.write_space();
13994                self.generate_table(owned)?;
13995            }
13996        }
13997
13998        Ok(())
13999    }
14000
14001    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
14002        self.write_keyword("DROP SEQUENCE");
14003
14004        if ds.if_exists {
14005            self.write_space();
14006            self.write_keyword("IF EXISTS");
14007        }
14008
14009        self.write_space();
14010        self.generate_table(&ds.name)?;
14011
14012        if ds.cascade {
14013            self.write_space();
14014            self.write_keyword("CASCADE");
14015        }
14016
14017        Ok(())
14018    }
14019
14020    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
14021        self.write_keyword("ALTER SEQUENCE");
14022
14023        if als.if_exists {
14024            self.write_space();
14025            self.write_keyword("IF EXISTS");
14026        }
14027
14028        self.write_space();
14029        self.generate_table(&als.name)?;
14030
14031        if let Some(inc) = als.increment {
14032            self.write_space();
14033            self.write_keyword("INCREMENT BY");
14034            self.write(&format!(" {}", inc));
14035        }
14036
14037        if let Some(min) = &als.minvalue {
14038            self.write_space();
14039            match min {
14040                SequenceBound::Value(v) => {
14041                    self.write_keyword("MINVALUE");
14042                    self.write(&format!(" {}", v));
14043                }
14044                SequenceBound::None => {
14045                    self.write_keyword("NO MINVALUE");
14046                }
14047            }
14048        }
14049
14050        if let Some(max) = &als.maxvalue {
14051            self.write_space();
14052            match max {
14053                SequenceBound::Value(v) => {
14054                    self.write_keyword("MAXVALUE");
14055                    self.write(&format!(" {}", v));
14056                }
14057                SequenceBound::None => {
14058                    self.write_keyword("NO MAXVALUE");
14059                }
14060            }
14061        }
14062
14063        if let Some(start) = als.start {
14064            self.write_space();
14065            self.write_keyword("START WITH");
14066            self.write(&format!(" {}", start));
14067        }
14068
14069        if let Some(restart) = &als.restart {
14070            self.write_space();
14071            self.write_keyword("RESTART");
14072            if let Some(val) = restart {
14073                self.write_keyword(" WITH");
14074                self.write(&format!(" {}", val));
14075            }
14076        }
14077
14078        if let Some(cache) = als.cache {
14079            self.write_space();
14080            self.write_keyword("CACHE");
14081            self.write(&format!(" {}", cache));
14082        }
14083
14084        if let Some(cycle) = als.cycle {
14085            self.write_space();
14086            if cycle {
14087                self.write_keyword("CYCLE");
14088            } else {
14089                self.write_keyword("NO CYCLE");
14090            }
14091        }
14092
14093        if let Some(owned) = &als.owned_by {
14094            self.write_space();
14095            self.write_keyword("OWNED BY");
14096            self.write_space();
14097            if let Some(table) = owned {
14098                self.generate_table(table)?;
14099            } else {
14100                self.write_keyword("NONE");
14101            }
14102        }
14103
14104        Ok(())
14105    }
14106
14107    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
14108        self.write_keyword("CREATE");
14109
14110        if ct.or_alter {
14111            self.write_space();
14112            self.write_keyword("OR ALTER");
14113        } else if ct.or_replace {
14114            self.write_space();
14115            self.write_keyword("OR REPLACE");
14116        }
14117
14118        if ct.constraint {
14119            self.write_space();
14120            self.write_keyword("CONSTRAINT");
14121        }
14122
14123        self.write_space();
14124        self.write_keyword("TRIGGER");
14125        self.write_space();
14126        self.generate_identifier(&ct.name)?;
14127
14128        self.write_space();
14129        match ct.timing {
14130            TriggerTiming::Before => self.write_keyword("BEFORE"),
14131            TriggerTiming::After => self.write_keyword("AFTER"),
14132            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
14133        }
14134
14135        // Events
14136        for (i, event) in ct.events.iter().enumerate() {
14137            if i > 0 {
14138                self.write_keyword(" OR");
14139            }
14140            self.write_space();
14141            match event {
14142                TriggerEvent::Insert => self.write_keyword("INSERT"),
14143                TriggerEvent::Update(cols) => {
14144                    self.write_keyword("UPDATE");
14145                    if let Some(cols) = cols {
14146                        self.write_space();
14147                        self.write_keyword("OF");
14148                        for (j, col) in cols.iter().enumerate() {
14149                            if j > 0 {
14150                                self.write(",");
14151                            }
14152                            self.write_space();
14153                            self.generate_identifier(col)?;
14154                        }
14155                    }
14156                }
14157                TriggerEvent::Delete => self.write_keyword("DELETE"),
14158                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
14159            }
14160        }
14161
14162        self.write_space();
14163        self.write_keyword("ON");
14164        self.write_space();
14165        self.generate_table(&ct.table)?;
14166
14167        // Referencing clause
14168        if let Some(ref_clause) = &ct.referencing {
14169            self.write_space();
14170            self.write_keyword("REFERENCING");
14171            if let Some(old_table) = &ref_clause.old_table {
14172                self.write_space();
14173                self.write_keyword("OLD TABLE AS");
14174                self.write_space();
14175                self.generate_identifier(old_table)?;
14176            }
14177            if let Some(new_table) = &ref_clause.new_table {
14178                self.write_space();
14179                self.write_keyword("NEW TABLE AS");
14180                self.write_space();
14181                self.generate_identifier(new_table)?;
14182            }
14183            if let Some(old_row) = &ref_clause.old_row {
14184                self.write_space();
14185                self.write_keyword("OLD ROW AS");
14186                self.write_space();
14187                self.generate_identifier(old_row)?;
14188            }
14189            if let Some(new_row) = &ref_clause.new_row {
14190                self.write_space();
14191                self.write_keyword("NEW ROW AS");
14192                self.write_space();
14193                self.generate_identifier(new_row)?;
14194            }
14195        }
14196
14197        // Deferrable options for constraint triggers (must come before FOR EACH)
14198        if let Some(deferrable) = ct.deferrable {
14199            self.write_space();
14200            if deferrable {
14201                self.write_keyword("DEFERRABLE");
14202            } else {
14203                self.write_keyword("NOT DEFERRABLE");
14204            }
14205        }
14206
14207        if let Some(initially) = ct.initially_deferred {
14208            self.write_space();
14209            self.write_keyword("INITIALLY");
14210            self.write_space();
14211            if initially {
14212                self.write_keyword("DEFERRED");
14213            } else {
14214                self.write_keyword("IMMEDIATE");
14215            }
14216        }
14217
14218        if let Some(for_each) = ct.for_each {
14219            self.write_space();
14220            self.write_keyword("FOR EACH");
14221            self.write_space();
14222            match for_each {
14223                TriggerForEach::Row => self.write_keyword("ROW"),
14224                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
14225            }
14226        }
14227
14228        // When clause
14229        if let Some(when) = &ct.when {
14230            self.write_space();
14231            self.write_keyword("WHEN");
14232            if ct.when_paren {
14233                self.write(" (");
14234                self.generate_expression(when)?;
14235                self.write(")");
14236            } else {
14237                self.write_space();
14238                self.generate_expression(when)?;
14239            }
14240        }
14241
14242        // Body
14243        self.write_space();
14244        match &ct.body {
14245            TriggerBody::Execute { function, args } => {
14246                self.write_keyword("EXECUTE FUNCTION");
14247                self.write_space();
14248                self.generate_table(function)?;
14249                self.write("(");
14250                for (i, arg) in args.iter().enumerate() {
14251                    if i > 0 {
14252                        self.write(", ");
14253                    }
14254                    self.generate_expression(arg)?;
14255                }
14256                self.write(")");
14257            }
14258            TriggerBody::Block(block) => {
14259                self.write_keyword("BEGIN");
14260                self.write_space();
14261                self.write(block);
14262                self.write_space();
14263                self.write_keyword("END");
14264            }
14265        }
14266
14267        Ok(())
14268    }
14269
14270    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
14271        self.write_keyword("DROP TRIGGER");
14272
14273        if dt.if_exists {
14274            self.write_space();
14275            self.write_keyword("IF EXISTS");
14276        }
14277
14278        self.write_space();
14279        self.generate_identifier(&dt.name)?;
14280
14281        if let Some(table) = &dt.table {
14282            self.write_space();
14283            self.write_keyword("ON");
14284            self.write_space();
14285            self.generate_table(table)?;
14286        }
14287
14288        if dt.cascade {
14289            self.write_space();
14290            self.write_keyword("CASCADE");
14291        }
14292
14293        Ok(())
14294    }
14295
14296    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
14297        self.write_keyword("CREATE TYPE");
14298
14299        if ct.if_not_exists {
14300            self.write_space();
14301            self.write_keyword("IF NOT EXISTS");
14302        }
14303
14304        self.write_space();
14305        self.generate_table(&ct.name)?;
14306
14307        if let TypeDefinition::Base {
14308            input,
14309            output,
14310            internallength,
14311        } = &ct.definition
14312        {
14313            if input.is_empty() && output.is_empty() && internallength.is_none() {
14314                return Ok(());
14315            }
14316        }
14317
14318        self.write_space();
14319        self.write_keyword("AS");
14320        self.write_space();
14321
14322        match &ct.definition {
14323            TypeDefinition::Enum(values) => {
14324                self.write_keyword("ENUM");
14325                self.write(" (");
14326                for (i, val) in values.iter().enumerate() {
14327                    if i > 0 {
14328                        self.write(", ");
14329                    }
14330                    self.write(&format!("'{}'", val));
14331                }
14332                self.write(")");
14333            }
14334            TypeDefinition::Composite(attrs) => {
14335                self.write("(");
14336                for (i, attr) in attrs.iter().enumerate() {
14337                    if i > 0 {
14338                        self.write(", ");
14339                    }
14340                    self.generate_identifier(&attr.name)?;
14341                    self.write_space();
14342                    self.generate_data_type(&attr.data_type)?;
14343                    if let Some(collate) = &attr.collate {
14344                        self.write_space();
14345                        self.write_keyword("COLLATE");
14346                        self.write_space();
14347                        self.generate_identifier(collate)?;
14348                    }
14349                }
14350                self.write(")");
14351            }
14352            TypeDefinition::Range {
14353                subtype,
14354                subtype_diff,
14355                canonical,
14356            } => {
14357                self.write_keyword("RANGE");
14358                self.write(" (");
14359                if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
14360                    self.write("subtype");
14361                } else {
14362                    self.write_keyword("SUBTYPE");
14363                }
14364                self.write(" = ");
14365                self.generate_data_type(subtype)?;
14366                if let Some(diff) = subtype_diff {
14367                    self.write(", ");
14368                    if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
14369                        self.write("subtype_diff");
14370                    } else {
14371                        self.write_keyword("SUBTYPE_DIFF");
14372                    }
14373                    self.write(" = ");
14374                    self.write(diff);
14375                }
14376                if let Some(canon) = canonical {
14377                    self.write(", ");
14378                    if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
14379                        self.write("canonical");
14380                    } else {
14381                        self.write_keyword("CANONICAL");
14382                    }
14383                    self.write(" = ");
14384                    self.write(canon);
14385                }
14386                self.write(")");
14387            }
14388            TypeDefinition::Base {
14389                input,
14390                output,
14391                internallength,
14392            } => {
14393                self.write("(");
14394                self.write_keyword("INPUT");
14395                self.write(" = ");
14396                self.write(input);
14397                self.write(", ");
14398                self.write_keyword("OUTPUT");
14399                self.write(" = ");
14400                self.write(output);
14401                if let Some(len) = internallength {
14402                    self.write(", ");
14403                    self.write_keyword("INTERNALLENGTH");
14404                    self.write(" = ");
14405                    self.write(&len.to_string());
14406                }
14407                self.write(")");
14408            }
14409            TypeDefinition::Domain {
14410                base_type,
14411                default,
14412                constraints,
14413            } => {
14414                self.generate_data_type(base_type)?;
14415                if let Some(def) = default {
14416                    self.write_space();
14417                    self.write_keyword("DEFAULT");
14418                    self.write_space();
14419                    self.generate_expression(def)?;
14420                }
14421                for constr in constraints {
14422                    self.write_space();
14423                    if let Some(name) = &constr.name {
14424                        self.write_keyword("CONSTRAINT");
14425                        self.write_space();
14426                        self.generate_identifier(name)?;
14427                        self.write_space();
14428                    }
14429                    self.write_keyword("CHECK");
14430                    self.write(" (");
14431                    self.generate_expression(&constr.check)?;
14432                    self.write(")");
14433                }
14434            }
14435        }
14436
14437        Ok(())
14438    }
14439
14440    fn generate_create_task(&mut self, task: &crate::expressions::CreateTask) -> Result<()> {
14441        self.write_keyword("CREATE");
14442        if task.or_replace {
14443            self.write_space();
14444            self.write_keyword("OR REPLACE");
14445        }
14446        self.write_space();
14447        self.write_keyword("TASK");
14448        if task.if_not_exists {
14449            self.write_space();
14450            self.write_keyword("IF NOT EXISTS");
14451        }
14452        self.write_space();
14453        self.write(&task.name);
14454        if !task.properties.is_empty() {
14455            // Properties already include leading whitespace from tokens_to_sql
14456            if !task.properties.starts_with('\n') && !task.properties.starts_with(' ') {
14457                self.write_space();
14458            }
14459            self.write(&task.properties);
14460        }
14461        self.write_space();
14462        self.write_keyword("AS");
14463        self.write_space();
14464        self.generate_expression(&task.body)?;
14465        Ok(())
14466    }
14467
14468    fn generate_try_catch(&mut self, try_catch: &TryCatch) -> Result<()> {
14469        self.write_keyword("BEGIN TRY");
14470        self.generate_tsql_block_statements(&try_catch.try_body)?;
14471        self.write_keyword("END TRY");
14472
14473        if let Some(catch_body) = &try_catch.catch_body {
14474            if self.config.pretty {
14475                self.write_newline();
14476                self.write_indent();
14477            } else {
14478                self.write_space();
14479            }
14480            self.write_keyword("BEGIN CATCH");
14481            self.generate_tsql_block_statements(catch_body)?;
14482            self.write_keyword("END CATCH");
14483        }
14484
14485        Ok(())
14486    }
14487
14488    fn generate_tsql_block_statements(&mut self, statements: &[Expression]) -> Result<()> {
14489        if statements.is_empty() {
14490            self.write_space();
14491            return Ok(());
14492        }
14493
14494        if self.config.pretty {
14495            self.indent_level += 1;
14496            for stmt in statements {
14497                self.write_newline();
14498                self.write_indent();
14499                self.generate_expression(stmt)?;
14500                self.write(";");
14501            }
14502            self.indent_level -= 1;
14503            self.write_newline();
14504            self.write_indent();
14505        } else {
14506            self.write_space();
14507            for (i, stmt) in statements.iter().enumerate() {
14508                if i > 0 {
14509                    self.write_space();
14510                }
14511                self.generate_expression(stmt)?;
14512                self.write(";");
14513            }
14514            self.write_space();
14515        }
14516
14517        Ok(())
14518    }
14519
14520    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
14521        self.write_keyword("DROP TYPE");
14522
14523        if dt.if_exists {
14524            self.write_space();
14525            self.write_keyword("IF EXISTS");
14526        }
14527
14528        self.write_space();
14529        self.generate_table(&dt.name)?;
14530
14531        if dt.cascade {
14532            self.write_space();
14533            self.write_keyword("CASCADE");
14534        }
14535
14536        Ok(())
14537    }
14538
14539    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
14540        // Athena: DESCRIBE uses Hive engine (backticks)
14541        let saved_athena_hive_context = self.athena_hive_context;
14542        if matches!(
14543            self.config.dialect,
14544            Some(crate::dialects::DialectType::Athena)
14545        ) {
14546            self.athena_hive_context = true;
14547        }
14548
14549        // Output leading comments before DESCRIBE
14550        for comment in &d.leading_comments {
14551            self.write_formatted_comment(comment);
14552            self.write(" ");
14553        }
14554
14555        self.write_keyword("DESCRIBE");
14556
14557        if d.extended {
14558            self.write_space();
14559            self.write_keyword("EXTENDED");
14560        } else if d.formatted {
14561            self.write_space();
14562            self.write_keyword("FORMATTED");
14563        }
14564
14565        // Output style like ANALYZE, HISTORY
14566        if let Some(ref style) = d.style {
14567            self.write_space();
14568            self.write_keyword(style);
14569        }
14570
14571        // Handle object kind (TABLE, VIEW) based on dialect
14572        let should_output_kind = match self.config.dialect {
14573            // Spark doesn't use TABLE/VIEW after DESCRIBE
14574            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
14575                false
14576            }
14577            // Snowflake always includes TABLE
14578            Some(DialectType::Snowflake) => true,
14579            _ => d.kind.is_some(),
14580        };
14581        if should_output_kind {
14582            if let Some(ref kind) = d.kind {
14583                self.write_space();
14584                self.write_keyword(kind);
14585            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
14586                self.write_space();
14587                self.write_keyword("TABLE");
14588            }
14589        }
14590
14591        self.write_space();
14592        self.generate_expression(&d.target)?;
14593
14594        // Output parenthesized parameter types for PROCEDURE/FUNCTION
14595        if !d.params.is_empty() {
14596            self.write("(");
14597            for (i, param) in d.params.iter().enumerate() {
14598                if i > 0 {
14599                    self.write(", ");
14600                }
14601                self.write(param);
14602            }
14603            self.write(")");
14604        }
14605
14606        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
14607        if let Some(ref partition) = d.partition {
14608            self.write_space();
14609            self.generate_expression(partition)?;
14610        }
14611
14612        // Databricks: AS JSON
14613        if d.as_json {
14614            self.write_space();
14615            self.write_keyword("AS JSON");
14616        }
14617
14618        // Output properties like type=stage
14619        for (name, value) in &d.properties {
14620            self.write_space();
14621            self.write(name);
14622            self.write("=");
14623            self.write(value);
14624        }
14625
14626        // Restore Athena Hive context
14627        self.athena_hive_context = saved_athena_hive_context;
14628
14629        Ok(())
14630    }
14631
14632    /// Generate SHOW statement (Snowflake, MySQL, etc.)
14633    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
14634    fn generate_show(&mut self, s: &Show) -> Result<()> {
14635        self.write_keyword("SHOW");
14636        self.write_space();
14637
14638        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
14639        // where TERSE is syntactically valid but has no effect on output
14640        let show_terse = s.terse
14641            && !matches!(
14642                s.this.as_str(),
14643                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
14644            );
14645        if show_terse {
14646            self.write_keyword("TERSE");
14647            self.write_space();
14648        }
14649
14650        // Object type (USERS, TABLES, DATABASES, etc.)
14651        self.write_keyword(&s.this);
14652
14653        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
14654        if let Some(ref target_expr) = s.target {
14655            self.write_space();
14656            self.generate_expression(target_expr)?;
14657        }
14658
14659        // HISTORY keyword
14660        if s.history {
14661            self.write_space();
14662            self.write_keyword("HISTORY");
14663        }
14664
14665        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
14666        if let Some(ref for_target) = s.for_target {
14667            self.write_space();
14668            self.write_keyword("FOR");
14669            self.write_space();
14670            self.generate_expression(for_target)?;
14671        }
14672
14673        // Determine ordering based on dialect:
14674        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
14675        // - MySQL: IN, FROM, LIKE (when FROM is present)
14676        use crate::dialects::DialectType;
14677        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
14678        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
14679        let mysql_tables_scope_as_from = is_mysql
14680            && matches!(s.this.as_str(), "TABLES" | "FULL TABLES")
14681            && s.scope_kind.as_deref() == Some("SCHEMA")
14682            && s.scope.is_some()
14683            && s.from.is_none();
14684
14685        if !is_snowflake && s.from.is_some() {
14686            // MySQL ordering: IN, FROM, LIKE
14687
14688            // IN scope_kind [scope]
14689            if let Some(ref scope_kind) = s.scope_kind {
14690                self.write_space();
14691                self.write_keyword("IN");
14692                self.write_space();
14693                self.write_keyword(scope_kind);
14694                if let Some(ref scope) = s.scope {
14695                    self.write_space();
14696                    self.generate_expression(scope)?;
14697                }
14698            } else if let Some(ref scope) = s.scope {
14699                self.write_space();
14700                self.write_keyword("IN");
14701                self.write_space();
14702                self.generate_expression(scope)?;
14703            }
14704
14705            // FROM clause
14706            if let Some(ref from) = s.from {
14707                self.write_space();
14708                self.write_keyword("FROM");
14709                self.write_space();
14710                self.generate_expression(from)?;
14711            }
14712
14713            // Second FROM clause (db name)
14714            if let Some(ref db) = s.db {
14715                self.write_space();
14716                self.write_keyword("FROM");
14717                self.write_space();
14718                self.generate_expression(db)?;
14719            }
14720
14721            // LIKE pattern
14722            if let Some(ref like) = s.like {
14723                self.write_space();
14724                self.write_keyword("LIKE");
14725                self.write_space();
14726                self.generate_expression(like)?;
14727            }
14728        } else {
14729            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
14730
14731            // LIKE pattern
14732            if let Some(ref like) = s.like {
14733                self.write_space();
14734                self.write_keyword("LIKE");
14735                self.write_space();
14736                self.generate_expression(like)?;
14737            }
14738
14739            // IN scope_kind [scope]
14740            if mysql_tables_scope_as_from {
14741                self.write_space();
14742                self.write_keyword("FROM");
14743                self.write_space();
14744                self.generate_expression(s.scope.as_ref().unwrap())?;
14745            } else if let Some(ref scope_kind) = s.scope_kind {
14746                self.write_space();
14747                self.write_keyword("IN");
14748                self.write_space();
14749                self.write_keyword(scope_kind);
14750                if let Some(ref scope) = s.scope {
14751                    self.write_space();
14752                    self.generate_expression(scope)?;
14753                }
14754            } else if let Some(ref scope) = s.scope {
14755                self.write_space();
14756                self.write_keyword("IN");
14757                self.write_space();
14758                self.generate_expression(scope)?;
14759            }
14760        }
14761
14762        // STARTS WITH pattern
14763        if let Some(ref starts_with) = s.starts_with {
14764            self.write_space();
14765            self.write_keyword("STARTS WITH");
14766            self.write_space();
14767            self.generate_expression(starts_with)?;
14768        }
14769
14770        // LIMIT clause
14771        if let Some(ref limit) = s.limit {
14772            self.write_space();
14773            self.generate_limit(limit)?;
14774        }
14775
14776        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
14777        if is_snowflake {
14778            if let Some(ref from) = s.from {
14779                self.write_space();
14780                self.write_keyword("FROM");
14781                self.write_space();
14782                self.generate_expression(from)?;
14783            }
14784        }
14785
14786        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
14787        if let Some(ref where_clause) = s.where_clause {
14788            self.write_space();
14789            self.write_keyword("WHERE");
14790            self.write_space();
14791            self.generate_expression(where_clause)?;
14792        }
14793
14794        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
14795        if let Some(is_mutex) = s.mutex {
14796            self.write_space();
14797            if is_mutex {
14798                self.write_keyword("MUTEX");
14799            } else {
14800                self.write_keyword("STATUS");
14801            }
14802        }
14803
14804        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
14805        if !s.privileges.is_empty() {
14806            self.write_space();
14807            self.write_keyword("WITH PRIVILEGES");
14808            self.write_space();
14809            for (i, priv_name) in s.privileges.iter().enumerate() {
14810                if i > 0 {
14811                    self.write(", ");
14812                }
14813                self.write_keyword(priv_name);
14814            }
14815        }
14816
14817        Ok(())
14818    }
14819
14820    // ==================== End DDL Generation ====================
14821
14822    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
14823        use crate::dialects::DialectType;
14824        match lit {
14825            Literal::String(s) => {
14826                self.generate_string_literal(s)?;
14827            }
14828            Literal::Number(n) => {
14829                if matches!(self.config.dialect, Some(DialectType::MySQL))
14830                    && n.len() > 2
14831                    && (n.starts_with("0x") || n.starts_with("0X"))
14832                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
14833                {
14834                    return self.generate_identifier(&Identifier {
14835                        name: n.clone(),
14836                        quoted: true,
14837                        trailing_comments: Vec::new(),
14838                        span: None,
14839                    });
14840                }
14841                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
14842                // for dialects that don't support them (MySQL interprets as identifier).
14843                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
14844                let n = if n.contains('_')
14845                    && !matches!(
14846                        self.config.dialect,
14847                        Some(DialectType::ClickHouse)
14848                            | Some(DialectType::DuckDB)
14849                            | Some(DialectType::PostgreSQL)
14850                            | Some(DialectType::Hive)
14851                            | Some(DialectType::Spark)
14852                            | Some(DialectType::Databricks)
14853                    ) {
14854                    std::borrow::Cow::Owned(n.replace('_', ""))
14855                } else {
14856                    std::borrow::Cow::Borrowed(n.as_str())
14857                };
14858                // Normalize numbers starting with decimal point to have leading zero
14859                // e.g., .25 -> 0.25 (matches sqlglot behavior)
14860                if n.starts_with('.') {
14861                    self.write("0");
14862                    self.write(&n);
14863                } else if n.starts_with("-.") {
14864                    // Handle negative numbers like -.25 -> -0.25
14865                    self.write("-0");
14866                    self.write(&n[1..]);
14867                } else {
14868                    self.write(&n);
14869                }
14870            }
14871            Literal::HexString(h) => {
14872                // Most dialects use lowercase x'...' for hex literals.
14873                match self.config.dialect {
14874                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
14875                        self.write("0x");
14876                        self.write(h);
14877                        return Ok(());
14878                    }
14879                    Some(DialectType::Spark)
14880                    | Some(DialectType::Databricks)
14881                    | Some(DialectType::Teradata) => self.write("X'"),
14882                    _ => self.write("x'"),
14883                }
14884                self.write(h);
14885                self.write("'");
14886            }
14887            Literal::HexNumber(h) => {
14888                // Hex number (0xA) - integer in hex notation (from BigQuery)
14889                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
14890                // For other dialects, convert to decimal integer
14891                match self.config.dialect {
14892                    Some(DialectType::BigQuery)
14893                    | Some(DialectType::ClickHouse)
14894                    | Some(DialectType::TSQL)
14895                    | Some(DialectType::Fabric) => {
14896                        self.write("0x");
14897                        self.write(h);
14898                    }
14899                    _ => {
14900                        // Convert hex to decimal
14901                        if let Ok(val) = u64::from_str_radix(h, 16) {
14902                            self.write(&val.to_string());
14903                        } else {
14904                            // Fallback: keep as 0x notation
14905                            self.write("0x");
14906                            self.write(h);
14907                        }
14908                    }
14909                }
14910            }
14911            Literal::BitString(b) => {
14912                // Bit string B'0101...'
14913                self.write("B'");
14914                self.write(b);
14915                self.write("'");
14916            }
14917            Literal::ByteString(b) => {
14918                // Byte string b'...' (BigQuery style)
14919                self.write("b'");
14920                // Escape special characters for output
14921                self.write_escaped_byte_string(b);
14922                self.write("'");
14923            }
14924            Literal::NationalString(s) => {
14925                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
14926                // Other dialects strip the N prefix and output as regular string
14927                let keep_n_prefix = matches!(
14928                    self.config.dialect,
14929                    Some(DialectType::TSQL)
14930                        | Some(DialectType::Oracle)
14931                        | Some(DialectType::MySQL)
14932                        | None
14933                );
14934                if keep_n_prefix {
14935                    self.write("N'");
14936                } else {
14937                    self.write("'");
14938                }
14939                self.write(s);
14940                self.write("'");
14941            }
14942            Literal::Date(d) => {
14943                self.generate_date_literal(d)?;
14944            }
14945            Literal::Time(t) => {
14946                self.generate_time_literal(t)?;
14947            }
14948            Literal::Timestamp(ts) => {
14949                self.generate_timestamp_literal(ts)?;
14950            }
14951            Literal::Datetime(dt) => {
14952                self.generate_datetime_literal(dt)?;
14953            }
14954            Literal::TripleQuotedString(s, _quote_char) => {
14955                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
14956                if matches!(
14957                    self.config.dialect,
14958                    Some(crate::dialects::DialectType::BigQuery)
14959                        | Some(crate::dialects::DialectType::DuckDB)
14960                        | Some(crate::dialects::DialectType::Snowflake)
14961                        | Some(crate::dialects::DialectType::Spark)
14962                        | Some(crate::dialects::DialectType::Hive)
14963                        | Some(crate::dialects::DialectType::Presto)
14964                        | Some(crate::dialects::DialectType::Trino)
14965                        | Some(crate::dialects::DialectType::PostgreSQL)
14966                        | Some(crate::dialects::DialectType::MySQL)
14967                        | Some(crate::dialects::DialectType::Redshift)
14968                        | Some(crate::dialects::DialectType::TSQL)
14969                        | Some(crate::dialects::DialectType::Oracle)
14970                        | Some(crate::dialects::DialectType::ClickHouse)
14971                        | Some(crate::dialects::DialectType::Databricks)
14972                        | Some(crate::dialects::DialectType::SQLite)
14973                ) {
14974                    self.generate_string_literal(s)?;
14975                } else {
14976                    // Preserve triple-quoted string syntax for generic/unknown dialects
14977                    let quotes = format!("{0}{0}{0}", _quote_char);
14978                    self.write(&quotes);
14979                    self.write(s);
14980                    self.write(&quotes);
14981                }
14982            }
14983            Literal::EscapeString(s) => {
14984                // PostgreSQL escape string: e'...' or E'...'
14985                // Token text format is "e:content" or "E:content"
14986                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
14987                use crate::dialects::DialectType;
14988                let content = if let Some(c) = s.strip_prefix("e:") {
14989                    c
14990                } else if let Some(c) = s.strip_prefix("E:") {
14991                    c
14992                } else {
14993                    s.as_str()
14994                };
14995
14996                // MySQL strips the PostgreSQL E prefix but still emits a string literal.
14997                if matches!(
14998                    self.config.dialect,
14999                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
15000                ) {
15001                    self.write("'");
15002                    self.write(&content.replace('\'', "''"));
15003                    self.write("'");
15004                } else {
15005                    // Some dialects use lowercase e' prefix
15006                    let prefix = if matches!(
15007                        self.config.dialect,
15008                        Some(DialectType::SingleStore)
15009                            | Some(DialectType::DuckDB)
15010                            | Some(DialectType::PostgreSQL)
15011                            | Some(DialectType::CockroachDB)
15012                            | Some(DialectType::Materialize)
15013                            | Some(DialectType::RisingWave)
15014                    ) {
15015                        "e'"
15016                    } else {
15017                        "E'"
15018                    };
15019
15020                    // Normalize \' to '' for output
15021                    let normalized = content.replace("\\'", "''");
15022                    self.write(prefix);
15023                    self.write(&normalized);
15024                    self.write("'");
15025                }
15026            }
15027            Literal::DollarString(s) => {
15028                // Convert dollar-quoted strings to single-quoted strings
15029                // (like Python sqlglot's rawstring_sql)
15030                use crate::dialects::DialectType;
15031                // Extract content from tag\x00content format
15032                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
15033                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
15034                let escape_backslash = matches!(
15035                    self.config.dialect,
15036                    Some(DialectType::ClickHouse) | Some(DialectType::Snowflake)
15037                );
15038                // Step 2: Determine quote escaping style
15039                // Snowflake: ' -> \' (backslash escape)
15040                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
15041                let use_backslash_quote =
15042                    matches!(self.config.dialect, Some(DialectType::Snowflake));
15043
15044                let mut escaped = String::with_capacity(content.len() + 4);
15045                for ch in content.chars() {
15046                    if escape_backslash && ch == '\\' {
15047                        // Escape backslash first (before quote escaping)
15048                        escaped.push('\\');
15049                        escaped.push('\\');
15050                    } else if ch == '\'' {
15051                        if use_backslash_quote {
15052                            escaped.push('\\');
15053                            escaped.push('\'');
15054                        } else {
15055                            escaped.push('\'');
15056                            escaped.push('\'');
15057                        }
15058                    } else {
15059                        escaped.push(ch);
15060                    }
15061                }
15062                self.write("'");
15063                self.write(&escaped);
15064                self.write("'");
15065            }
15066            Literal::RawString(s) => {
15067                // Raw strings (r"..." or r'...') contain literal backslashes.
15068                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
15069                // 1. If \\ is in STRING_ESCAPES, double all backslashes
15070                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
15071                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
15072                use crate::dialects::DialectType;
15073
15074                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
15075                let escape_backslash = matches!(
15076                    self.config.dialect,
15077                    Some(DialectType::BigQuery)
15078                        | Some(DialectType::MySQL)
15079                        | Some(DialectType::SingleStore)
15080                        | Some(DialectType::TiDB)
15081                        | Some(DialectType::Hive)
15082                        | Some(DialectType::Spark)
15083                        | Some(DialectType::Databricks)
15084                        | Some(DialectType::Drill)
15085                        | Some(DialectType::Snowflake)
15086                        | Some(DialectType::Redshift)
15087                        | Some(DialectType::ClickHouse)
15088                );
15089
15090                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
15091                // These escape quotes as \' instead of ''
15092                let backslash_escapes_quote = matches!(
15093                    self.config.dialect,
15094                    Some(DialectType::BigQuery)
15095                        | Some(DialectType::Hive)
15096                        | Some(DialectType::Spark)
15097                        | Some(DialectType::Databricks)
15098                        | Some(DialectType::Drill)
15099                        | Some(DialectType::Snowflake)
15100                        | Some(DialectType::Redshift)
15101                );
15102
15103                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
15104                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
15105                let supports_escape_sequences = escape_backslash;
15106
15107                let mut escaped = String::with_capacity(s.len() + 4);
15108                for ch in s.chars() {
15109                    if escape_backslash && ch == '\\' {
15110                        // Double the backslash for the target dialect
15111                        escaped.push('\\');
15112                        escaped.push('\\');
15113                    } else if ch == '\'' {
15114                        if backslash_escapes_quote {
15115                            // Use backslash to escape the quote: \'
15116                            escaped.push('\\');
15117                            escaped.push('\'');
15118                        } else {
15119                            // Use SQL standard quote doubling: ''
15120                            escaped.push('\'');
15121                            escaped.push('\'');
15122                        }
15123                    } else if supports_escape_sequences {
15124                        // Apply ESCAPED_SEQUENCES mapping for special chars
15125                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
15126                        match ch {
15127                            '\n' => {
15128                                escaped.push('\\');
15129                                escaped.push('n');
15130                            }
15131                            '\r' => {
15132                                escaped.push('\\');
15133                                escaped.push('r');
15134                            }
15135                            '\t' => {
15136                                escaped.push('\\');
15137                                escaped.push('t');
15138                            }
15139                            '\x07' => {
15140                                escaped.push('\\');
15141                                escaped.push('a');
15142                            }
15143                            '\x08' => {
15144                                escaped.push('\\');
15145                                escaped.push('b');
15146                            }
15147                            '\x0C' => {
15148                                escaped.push('\\');
15149                                escaped.push('f');
15150                            }
15151                            '\x0B' => {
15152                                escaped.push('\\');
15153                                escaped.push('v');
15154                            }
15155                            _ => escaped.push(ch),
15156                        }
15157                    } else {
15158                        escaped.push(ch);
15159                    }
15160                }
15161                self.write("'");
15162                self.write(&escaped);
15163                self.write("'");
15164            }
15165        }
15166        Ok(())
15167    }
15168
15169    /// Generate a DATE literal with dialect-specific formatting
15170    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
15171        use crate::dialects::DialectType;
15172
15173        match self.config.dialect {
15174            // SQL Server / Fabric use CONVERT or CAST
15175            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
15176                self.write("CAST('");
15177                self.write(d);
15178                self.write("' AS DATE)");
15179            }
15180            // BigQuery uses CAST syntax for type literals
15181            // DATE 'value' -> CAST('value' AS DATE)
15182            Some(DialectType::BigQuery) => {
15183                self.write("CAST('");
15184                self.write(d);
15185                self.write("' AS DATE)");
15186            }
15187            // Exasol uses CAST syntax for DATE literals
15188            // DATE 'value' -> CAST('value' AS DATE)
15189            Some(DialectType::Exasol) => {
15190                self.write("CAST('");
15191                self.write(d);
15192                self.write("' AS DATE)");
15193            }
15194            // Snowflake uses CAST syntax for DATE literals
15195            // DATE 'value' -> CAST('value' AS DATE)
15196            Some(DialectType::Snowflake) => {
15197                self.write("CAST('");
15198                self.write(d);
15199                self.write("' AS DATE)");
15200            }
15201            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
15202            Some(DialectType::PostgreSQL)
15203            | Some(DialectType::MySQL)
15204            | Some(DialectType::SingleStore)
15205            | Some(DialectType::TiDB)
15206            | Some(DialectType::Redshift) => {
15207                self.write("CAST('");
15208                self.write(d);
15209                self.write("' AS DATE)");
15210            }
15211            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
15212            Some(DialectType::DuckDB)
15213            | Some(DialectType::Presto)
15214            | Some(DialectType::Trino)
15215            | Some(DialectType::Athena)
15216            | Some(DialectType::Spark)
15217            | Some(DialectType::Databricks)
15218            | Some(DialectType::Hive) => {
15219                self.write("CAST('");
15220                self.write(d);
15221                self.write("' AS DATE)");
15222            }
15223            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
15224            Some(DialectType::Oracle) => {
15225                self.write("TO_DATE('");
15226                self.write(d);
15227                self.write("', 'YYYY-MM-DD')");
15228            }
15229            // Standard SQL: DATE '...'
15230            _ => {
15231                self.write_keyword("DATE");
15232                self.write(" '");
15233                self.write(d);
15234                self.write("'");
15235            }
15236        }
15237        Ok(())
15238    }
15239
15240    /// Generate a TIME literal with dialect-specific formatting
15241    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
15242        use crate::dialects::DialectType;
15243
15244        match self.config.dialect {
15245            // SQL Server uses CONVERT or CAST
15246            Some(DialectType::TSQL) => {
15247                self.write("CAST('");
15248                self.write(t);
15249                self.write("' AS TIME)");
15250            }
15251            // Standard SQL: TIME '...'
15252            _ => {
15253                self.write_keyword("TIME");
15254                self.write(" '");
15255                self.write(t);
15256                self.write("'");
15257            }
15258        }
15259        Ok(())
15260    }
15261
15262    /// Generate a date expression for Dremio, converting DATE literals to CAST
15263    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
15264        use crate::expressions::Literal;
15265
15266        match expr {
15267            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
15268                let Literal::Date(d) = lit.as_ref() else {
15269                    unreachable!()
15270                };
15271                // DATE 'value' -> CAST('value' AS DATE)
15272                self.write("CAST('");
15273                self.write(d);
15274                self.write("' AS DATE)");
15275            }
15276            _ => {
15277                // For all other expressions, generate normally
15278                self.generate_expression(expr)?;
15279            }
15280        }
15281        Ok(())
15282    }
15283
15284    /// Generate a TIMESTAMP literal with dialect-specific formatting
15285    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
15286        use crate::dialects::DialectType;
15287
15288        match self.config.dialect {
15289            // SQL Server uses CONVERT or CAST
15290            Some(DialectType::TSQL) => {
15291                self.write("CAST('");
15292                self.write(ts);
15293                self.write("' AS DATETIME2)");
15294            }
15295            // BigQuery uses CAST syntax for type literals
15296            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15297            Some(DialectType::BigQuery) => {
15298                self.write("CAST('");
15299                self.write(ts);
15300                self.write("' AS TIMESTAMP)");
15301            }
15302            // Snowflake uses CAST syntax for TIMESTAMP literals
15303            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15304            Some(DialectType::Snowflake) => {
15305                self.write("CAST('");
15306                self.write(ts);
15307                self.write("' AS TIMESTAMP)");
15308            }
15309            // Dremio uses CAST syntax for TIMESTAMP literals
15310            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15311            Some(DialectType::Dremio) => {
15312                self.write("CAST('");
15313                self.write(ts);
15314                self.write("' AS TIMESTAMP)");
15315            }
15316            // Exasol uses CAST syntax for TIMESTAMP literals
15317            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15318            Some(DialectType::Exasol) => {
15319                self.write("CAST('");
15320                self.write(ts);
15321                self.write("' AS TIMESTAMP)");
15322            }
15323            // Oracle prefers TO_TIMESTAMP function call
15324            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
15325            Some(DialectType::Oracle) => {
15326                self.write("TO_TIMESTAMP('");
15327                self.write(ts);
15328                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
15329            }
15330            // Presto/Trino: always use CAST for TIMESTAMP literals
15331            Some(DialectType::Presto) | Some(DialectType::Trino) => {
15332                if Self::timestamp_has_timezone(ts) {
15333                    self.write("CAST('");
15334                    self.write(ts);
15335                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
15336                } else {
15337                    self.write("CAST('");
15338                    self.write(ts);
15339                    self.write("' AS TIMESTAMP)");
15340                }
15341            }
15342            // ClickHouse: CAST('...' AS Nullable(DateTime))
15343            Some(DialectType::ClickHouse) => {
15344                self.write("CAST('");
15345                self.write(ts);
15346                self.write("' AS Nullable(DateTime))");
15347            }
15348            // Spark: CAST('...' AS TIMESTAMP)
15349            Some(DialectType::Spark) => {
15350                self.write("CAST('");
15351                self.write(ts);
15352                self.write("' AS TIMESTAMP)");
15353            }
15354            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
15355            // but TIMESTAMP '...' for special values like 'epoch'
15356            Some(DialectType::Redshift) => {
15357                if ts == "epoch" {
15358                    self.write_keyword("TIMESTAMP");
15359                    self.write(" '");
15360                    self.write(ts);
15361                    self.write("'");
15362                } else {
15363                    self.write("CAST('");
15364                    self.write(ts);
15365                    self.write("' AS TIMESTAMP)");
15366                }
15367            }
15368            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
15369            Some(DialectType::PostgreSQL)
15370            | Some(DialectType::Hive)
15371            | Some(DialectType::SQLite)
15372            | Some(DialectType::DuckDB)
15373            | Some(DialectType::Athena)
15374            | Some(DialectType::Drill)
15375            | Some(DialectType::Teradata) => {
15376                self.write("CAST('");
15377                self.write(ts);
15378                self.write("' AS TIMESTAMP)");
15379            }
15380            // MySQL/StarRocks: CAST('...' AS DATETIME)
15381            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
15382                self.write("CAST('");
15383                self.write(ts);
15384                self.write("' AS DATETIME)");
15385            }
15386            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
15387            Some(DialectType::Databricks) => {
15388                self.write("CAST('");
15389                self.write(ts);
15390                self.write("' AS TIMESTAMP_NTZ)");
15391            }
15392            // Standard SQL: TIMESTAMP '...'
15393            _ => {
15394                self.write_keyword("TIMESTAMP");
15395                self.write(" '");
15396                self.write(ts);
15397                self.write("'");
15398            }
15399        }
15400        Ok(())
15401    }
15402
15403    /// Check if a timestamp string contains a timezone identifier
15404    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
15405    fn timestamp_has_timezone(ts: &str) -> bool {
15406        // Check for common IANA timezone patterns: Continent/City format
15407        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
15408        // Also handles: UTC, GMT, Etc/GMT+0, etc.
15409        let ts_lower = ts.to_ascii_lowercase();
15410
15411        // Check for Continent/City pattern (most common)
15412        let continent_prefixes = [
15413            "africa/",
15414            "america/",
15415            "antarctica/",
15416            "arctic/",
15417            "asia/",
15418            "atlantic/",
15419            "australia/",
15420            "europe/",
15421            "indian/",
15422            "pacific/",
15423            "etc/",
15424            "brazil/",
15425            "canada/",
15426            "chile/",
15427            "mexico/",
15428            "us/",
15429        ];
15430
15431        for prefix in &continent_prefixes {
15432            if ts_lower.contains(prefix) {
15433                return true;
15434            }
15435        }
15436
15437        // Check for standalone timezone abbreviations at the end
15438        // These typically appear after the time portion
15439        let tz_abbrevs = [
15440            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
15441            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
15442            " sgt", " aest", " aedt", " acst", " acdt", " awst",
15443        ];
15444
15445        for abbrev in &tz_abbrevs {
15446            if ts_lower.ends_with(abbrev) {
15447                return true;
15448            }
15449        }
15450
15451        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
15452        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
15453        // Look for pattern: space followed by + or - and digits (optionally with :)
15454        let trimmed = ts.trim();
15455        if let Some(last_space) = trimmed.rfind(' ') {
15456            let suffix = &trimmed[last_space + 1..];
15457            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
15458                // Check if rest is numeric (possibly with : for hh:mm format)
15459                let rest = &suffix[1..];
15460                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
15461                    return true;
15462                }
15463            }
15464        }
15465
15466        false
15467    }
15468
15469    /// Generate a DATETIME literal with dialect-specific formatting
15470    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
15471        use crate::dialects::DialectType;
15472
15473        match self.config.dialect {
15474            // BigQuery uses CAST syntax for type literals
15475            // DATETIME 'value' -> CAST('value' AS DATETIME)
15476            Some(DialectType::BigQuery) => {
15477                self.write("CAST('");
15478                self.write(dt);
15479                self.write("' AS DATETIME)");
15480            }
15481            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
15482            Some(DialectType::DuckDB) => {
15483                self.write("CAST('");
15484                self.write(dt);
15485                self.write("' AS TIMESTAMP)");
15486            }
15487            // DATETIME is primarily a BigQuery type
15488            // Output as DATETIME '...' for dialects that support it
15489            _ => {
15490                self.write_keyword("DATETIME");
15491                self.write(" '");
15492                self.write(dt);
15493                self.write("'");
15494            }
15495        }
15496        Ok(())
15497    }
15498
15499    /// Generate a string literal with dialect-specific escaping
15500    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
15501        use crate::dialects::DialectType;
15502
15503        match self.config.dialect {
15504            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
15505            // and backslash escaping for special characters like newlines
15506            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
15507            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
15508                // Hive/Spark use backslash escaping for quotes (\') and special chars
15509                self.write("'");
15510                for c in s.chars() {
15511                    match c {
15512                        '\'' => self.write("\\'"),
15513                        '\\' => self.write("\\\\"),
15514                        '\n' => self.write("\\n"),
15515                        '\r' => self.write("\\r"),
15516                        '\t' => self.write("\\t"),
15517                        '\0' => self.write("\\0"),
15518                        _ => self.output.push(c),
15519                    }
15520                }
15521                self.write("'");
15522            }
15523            Some(DialectType::Drill) => {
15524                // Drill uses SQL-standard quote doubling ('') for quotes,
15525                // but backslash escaping for special characters
15526                self.write("'");
15527                for c in s.chars() {
15528                    match c {
15529                        '\'' => self.write("''"),
15530                        '\\' => self.write("\\\\"),
15531                        '\n' => self.write("\\n"),
15532                        '\r' => self.write("\\r"),
15533                        '\t' => self.write("\\t"),
15534                        '\0' => self.write("\\0"),
15535                        _ => self.output.push(c),
15536                    }
15537                }
15538                self.write("'");
15539            }
15540            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
15541                self.write("'");
15542                for c in s.chars() {
15543                    match c {
15544                        // MySQL uses SQL standard quote doubling
15545                        '\'' => self.write("''"),
15546                        '\\' => self.write("\\\\"),
15547                        '\n' => self.write("\\n"),
15548                        '\r' => self.write("\\r"),
15549                        '\t' => self.write("\\t"),
15550                        // sqlglot writes a literal NUL for this case
15551                        '\0' => self.output.push('\0'),
15552                        _ => self.output.push(c),
15553                    }
15554                }
15555                self.write("'");
15556            }
15557            // BigQuery: Uses backslash escaping
15558            Some(DialectType::BigQuery) => {
15559                self.write("'");
15560                for c in s.chars() {
15561                    match c {
15562                        '\'' => self.write("\\'"),
15563                        '\\' => self.write("\\\\"),
15564                        '\n' => self.write("\\n"),
15565                        '\r' => self.write("\\r"),
15566                        '\t' => self.write("\\t"),
15567                        '\0' => self.write("\\0"),
15568                        '\x07' => self.write("\\a"),
15569                        '\x08' => self.write("\\b"),
15570                        '\x0C' => self.write("\\f"),
15571                        '\x0B' => self.write("\\v"),
15572                        _ => self.output.push(c),
15573                    }
15574                }
15575                self.write("'");
15576            }
15577            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
15578            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
15579            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
15580            Some(DialectType::Athena) => {
15581                if self.athena_hive_context {
15582                    // Hive-style: backslash escaping
15583                    self.write("'");
15584                    for c in s.chars() {
15585                        match c {
15586                            '\'' => self.write("\\'"),
15587                            '\\' => self.write("\\\\"),
15588                            '\n' => self.write("\\n"),
15589                            '\r' => self.write("\\r"),
15590                            '\t' => self.write("\\t"),
15591                            '\0' => self.write("\\0"),
15592                            _ => self.output.push(c),
15593                        }
15594                    }
15595                    self.write("'");
15596                } else {
15597                    // Trino-style: SQL-standard escaping, preserve backslashes
15598                    self.write("'");
15599                    for c in s.chars() {
15600                        match c {
15601                            '\'' => self.write("''"),
15602                            // Preserve backslashes literally (no re-escaping)
15603                            _ => self.output.push(c),
15604                        }
15605                    }
15606                    self.write("'");
15607                }
15608            }
15609            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
15610            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
15611            // becomes string value '\\'), so we should NOT re-escape backslashes.
15612            // We only need to escape single quotes.
15613            Some(DialectType::Snowflake) => {
15614                self.write("'");
15615                for c in s.chars() {
15616                    match c {
15617                        '\'' => self.write("\\'"),
15618                        // Backslashes are already escaped in the tokenized string, don't re-escape
15619                        // Only escape special characters that might not have been escaped
15620                        '\n' => self.write("\\n"),
15621                        '\r' => self.write("\\r"),
15622                        '\t' => self.write("\\t"),
15623                        _ => self.output.push(c),
15624                    }
15625                }
15626                self.write("'");
15627            }
15628            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
15629            Some(DialectType::PostgreSQL) => {
15630                self.write("'");
15631                for c in s.chars() {
15632                    match c {
15633                        '\'' => self.write("''"),
15634                        _ => self.output.push(c),
15635                    }
15636                }
15637                self.write("'");
15638            }
15639            // Redshift: Uses backslash escaping for single quotes
15640            Some(DialectType::Redshift) => {
15641                self.write("'");
15642                for c in s.chars() {
15643                    match c {
15644                        '\'' => self.write("\\'"),
15645                        _ => self.output.push(c),
15646                    }
15647                }
15648                self.write("'");
15649            }
15650            // Oracle: Uses standard double single-quote escaping
15651            Some(DialectType::Oracle) => {
15652                self.write("'");
15653                for ch in s.chars() {
15654                    if ch == '\'' {
15655                        self.output.push_str("''");
15656                    } else {
15657                        self.output.push(ch);
15658                    }
15659                }
15660                self.write("'");
15661            }
15662            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
15663            // backslash escaping for backslashes and special characters
15664            Some(DialectType::ClickHouse) => {
15665                self.write("'");
15666                for c in s.chars() {
15667                    match c {
15668                        '\'' => self.write("''"),
15669                        '\\' => self.write("\\\\"),
15670                        '\n' => self.write("\\n"),
15671                        '\r' => self.write("\\r"),
15672                        '\t' => self.write("\\t"),
15673                        '\0' => self.write("\\0"),
15674                        '\x07' => self.write("\\a"),
15675                        '\x08' => self.write("\\b"),
15676                        '\x0C' => self.write("\\f"),
15677                        '\x0B' => self.write("\\v"),
15678                        // Non-printable characters: emit as \xNN hex escapes
15679                        c if c.is_control() || (c as u32) < 0x20 => {
15680                            let byte = c as u32;
15681                            if byte < 256 {
15682                                self.write(&format!("\\x{:02X}", byte));
15683                            } else {
15684                                self.output.push(c);
15685                            }
15686                        }
15687                        _ => self.output.push(c),
15688                    }
15689                }
15690                self.write("'");
15691            }
15692            // Default: SQL standard double single quotes (works for most dialects)
15693            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
15694            _ => {
15695                self.write("'");
15696                for ch in s.chars() {
15697                    if ch == '\'' {
15698                        self.output.push_str("''");
15699                    } else {
15700                        self.output.push(ch);
15701                    }
15702                }
15703                self.write("'");
15704            }
15705        }
15706        Ok(())
15707    }
15708
15709    /// Write a byte string with proper escaping for BigQuery-style byte literals
15710    /// Escapes characters as \xNN hex escapes where needed
15711    fn write_escaped_byte_string(&mut self, s: &str) {
15712        for c in s.chars() {
15713            match c {
15714                // Escape single quotes
15715                '\'' => self.write("\\'"),
15716                // Escape backslashes
15717                '\\' => self.write("\\\\"),
15718                // Keep all printable characters (including non-ASCII) as-is
15719                _ if !c.is_control() => self.output.push(c),
15720                // Escape control characters as hex
15721                _ => {
15722                    let byte = c as u32;
15723                    if byte < 256 {
15724                        self.write(&format!("\\x{:02x}", byte));
15725                    } else {
15726                        // For unicode characters, write each UTF-8 byte
15727                        for b in c.to_string().as_bytes() {
15728                            self.write(&format!("\\x{:02x}", b));
15729                        }
15730                    }
15731                }
15732            }
15733        }
15734    }
15735
15736    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
15737        use crate::dialects::DialectType;
15738
15739        // Different dialects have different boolean literal formats
15740        match self.config.dialect {
15741            // SQL Server typically uses 1/0 for boolean literals in many contexts
15742            // However, TRUE/FALSE also works in modern versions
15743            Some(DialectType::TSQL) => {
15744                self.write(if b.value { "1" } else { "0" });
15745            }
15746            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
15747            Some(DialectType::Oracle) => {
15748                self.write(if b.value { "1" } else { "0" });
15749            }
15750            // MySQL accepts TRUE/FALSE as aliases for 1/0
15751            Some(DialectType::MySQL) => {
15752                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15753            }
15754            // Most other dialects support TRUE/FALSE
15755            _ => {
15756                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15757            }
15758        }
15759        Ok(())
15760    }
15761
15762    /// Generate an identifier that's used as an alias name
15763    /// This quotes reserved keywords in addition to already-quoted identifiers
15764    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
15765        let name = &id.name;
15766        let quote_style = &self.config.identifier_quote_style;
15767
15768        // For aliases, quote if:
15769        // 1. The identifier was explicitly quoted in the source
15770        // 2. The identifier is a reserved keyword for the current dialect
15771        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
15772
15773        // Normalize identifier if configured
15774        let output_name = if self.config.normalize_identifiers && !id.quoted {
15775            name.to_ascii_lowercase()
15776        } else {
15777            name.to_string()
15778        };
15779
15780        if needs_quoting {
15781            let quote_style = if matches!(self.config.dialect, Some(DialectType::ClickHouse))
15782                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
15783                && quote_style.start == '"'
15784                && output_name.contains('"')
15785            {
15786                &IdentifierQuoteStyle::BACKTICK
15787            } else {
15788                quote_style
15789            };
15790            // Escape any quote characters within the identifier
15791            let escaped_name = if quote_style.start == quote_style.end {
15792                output_name.replace(
15793                    quote_style.end,
15794                    &format!("{}{}", quote_style.end, quote_style.end),
15795                )
15796            } else {
15797                output_name.replace(
15798                    quote_style.end,
15799                    &format!("{}{}", quote_style.end, quote_style.end),
15800                )
15801            };
15802            self.write(&format!(
15803                "{}{}{}",
15804                quote_style.start, escaped_name, quote_style.end
15805            ));
15806        } else {
15807            self.write(&output_name);
15808        }
15809
15810        // Output trailing comments
15811        for comment in &id.trailing_comments {
15812            self.write(" ");
15813            self.write_formatted_comment(comment);
15814        }
15815        Ok(())
15816    }
15817
15818    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
15819        use crate::dialects::DialectType;
15820
15821        let name = &id.name;
15822
15823        // For Athena, use backticks in Hive context, double quotes in Trino context
15824        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
15825            && self.athena_hive_context
15826        {
15827            &IdentifierQuoteStyle::BACKTICK
15828        } else {
15829            &self.config.identifier_quote_style
15830        };
15831
15832        // Quote if:
15833        // 1. The identifier was explicitly quoted in the source
15834        // 2. The identifier is a reserved keyword for the current dialect
15835        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
15836        // This matches Python sqlglot's identifier_sql behavior
15837        // Also quote identifiers starting with digits if the target dialect doesn't support them
15838        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
15839        let needs_digit_quoting = starts_with_digit
15840            && !self.config.identifiers_can_start_with_digit
15841            && self.config.dialect.is_some();
15842        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
15843            && name.len() > 2
15844            && (name.starts_with("0x") || name.starts_with("0X"))
15845            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
15846        let clickhouse_unsafe_identifier =
15847            matches!(self.config.dialect, Some(DialectType::ClickHouse))
15848                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
15849                && !name.starts_with('{')
15850                && !name.contains('(')
15851                && !name.contains(')')
15852                && name != "?"
15853                && name
15854                    .chars()
15855                    .any(|c| !(c.is_ascii_alphanumeric() || c == '_'));
15856        let needs_quoting = id.quoted
15857            || self.is_reserved_keyword(name)
15858            || self.config.always_quote_identifiers
15859            || needs_digit_quoting
15860            || mysql_invalid_hex_identifier
15861            || clickhouse_unsafe_identifier;
15862
15863        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
15864        // When quoted, we need to output `name`(16) not `name(16)`
15865        let (base_name, suffix) = if needs_quoting {
15866            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
15867            if let Some(paren_pos) = name.find('(') {
15868                let base = &name[..paren_pos];
15869                let rest = &name[paren_pos..];
15870                // Verify it looks like (digits) or (digits) ASC/DESC
15871                if rest.starts_with('(')
15872                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
15873                {
15874                    // Check if content between parens is all digits
15875                    let close_paren = rest.find(')').unwrap_or(rest.len());
15876                    let inside = &rest[1..close_paren];
15877                    if inside.chars().all(|c| c.is_ascii_digit()) {
15878                        (base.to_string(), rest.to_string())
15879                    } else {
15880                        (name.to_string(), String::new())
15881                    }
15882                } else {
15883                    (name.to_string(), String::new())
15884                }
15885            } else if name.ends_with(" ASC") {
15886                let base = &name[..name.len() - 4];
15887                (base.to_string(), " ASC".to_string())
15888            } else if name.ends_with(" DESC") {
15889                let base = &name[..name.len() - 5];
15890                (base.to_string(), " DESC".to_string())
15891            } else {
15892                (name.to_string(), String::new())
15893            }
15894        } else {
15895            (name.to_string(), String::new())
15896        };
15897
15898        // Normalize identifier if configured, with special handling for Exasol
15899        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
15900        // should be uppercased when not already quoted (to match Python sqlglot behavior)
15901        let output_name = if self.config.normalize_identifiers && !id.quoted {
15902            base_name.to_ascii_lowercase()
15903        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
15904            && !id.quoted
15905            && self.is_reserved_keyword(name)
15906        {
15907            // Exasol: uppercase reserved keywords when quoting them
15908            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
15909            base_name.to_ascii_uppercase()
15910        } else {
15911            base_name
15912        };
15913
15914        if needs_quoting {
15915            // Escape any quote characters within the identifier
15916            let escaped_name = if quote_style.start == quote_style.end {
15917                // Same start/end char (e.g., " or `) - double the quote char
15918                output_name.replace(
15919                    quote_style.end,
15920                    &format!("{}{}", quote_style.end, quote_style.end),
15921                )
15922            } else {
15923                // Different start/end (e.g., [ and ]) - escape only the end char
15924                output_name.replace(
15925                    quote_style.end,
15926                    &format!("{}{}", quote_style.end, quote_style.end),
15927                )
15928            };
15929            self.write(&format!(
15930                "{}{}{}{}",
15931                quote_style.start, escaped_name, quote_style.end, suffix
15932            ));
15933        } else {
15934            self.write(&output_name);
15935        }
15936
15937        // Output trailing comments
15938        for comment in &id.trailing_comments {
15939            self.write(" ");
15940            self.write_formatted_comment(comment);
15941        }
15942        Ok(())
15943    }
15944
15945    fn generate_column(&mut self, col: &Column) -> Result<()> {
15946        use crate::dialects::DialectType;
15947
15948        if let Some(table) = &col.table {
15949            // Exasol special case: LOCAL as column table prefix should NOT be quoted
15950            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
15951            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
15952            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
15953                && !table.quoted
15954                && table.name.eq_ignore_ascii_case("LOCAL");
15955
15956            if is_exasol_local_prefix {
15957                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
15958                self.write("LOCAL");
15959            } else {
15960                self.generate_identifier(table)?;
15961            }
15962            self.write(".");
15963        }
15964        self.generate_identifier(&col.name)?;
15965        // Oracle-style join marker (+)
15966        // Only output if dialect supports it (Oracle, Exasol)
15967        if col.join_mark && self.config.supports_column_join_marks {
15968            self.write(" (+)");
15969        }
15970        // Output trailing comments
15971        for comment in &col.trailing_comments {
15972            self.write_space();
15973            self.write_formatted_comment(comment);
15974        }
15975        Ok(())
15976    }
15977
15978    fn generate_prepare(&mut self, prepare: &PrepareStatement) -> Result<()> {
15979        self.write_keyword("PREPARE");
15980        self.write_space();
15981        self.generate_identifier(&prepare.name)?;
15982
15983        if !prepare.parameter_types.is_empty() {
15984            self.write(" (");
15985            for (i, data_type) in prepare.parameter_types.iter().enumerate() {
15986                if i > 0 {
15987                    self.write(", ");
15988                }
15989                self.generate_data_type(data_type)?;
15990            }
15991            self.write(")");
15992        }
15993
15994        self.write_space();
15995        self.write_keyword("AS");
15996        self.write_space();
15997        self.generate_expression(&prepare.statement)
15998    }
15999
16000    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
16001    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
16002    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
16003        use crate::dialects::DialectType;
16004        use crate::expressions::PseudocolumnType;
16005
16006        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
16007        if pc.kind == PseudocolumnType::Sysdate
16008            && !matches!(
16009                self.config.dialect,
16010                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
16011            )
16012        {
16013            self.write_keyword("CURRENT_TIMESTAMP");
16014            // Add () for dialects that expect it
16015            if matches!(
16016                self.config.dialect,
16017                Some(DialectType::MySQL)
16018                    | Some(DialectType::ClickHouse)
16019                    | Some(DialectType::Spark)
16020                    | Some(DialectType::Databricks)
16021                    | Some(DialectType::Hive)
16022            ) {
16023                self.write("()");
16024            }
16025        } else {
16026            self.write(pc.kind.as_str());
16027        }
16028        Ok(())
16029    }
16030
16031    /// Generate CONNECT BY clause (Oracle hierarchical queries)
16032    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
16033        use crate::dialects::DialectType;
16034
16035        // Generate native CONNECT BY for Oracle and Snowflake
16036        // For other dialects, add a comment noting manual conversion needed
16037        let supports_connect_by = matches!(
16038            self.config.dialect,
16039            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
16040        );
16041
16042        if !supports_connect_by && self.config.dialect.is_some() {
16043            // Add comment for unsupported dialects
16044            if self.config.pretty {
16045                self.write_newline();
16046            } else {
16047                self.write_space();
16048            }
16049            self.write_unsupported_comment(
16050                "CONNECT BY requires manual conversion to recursive CTE",
16051            )?;
16052        }
16053
16054        // Generate START WITH if present (before CONNECT BY)
16055        if let Some(start) = &connect.start {
16056            if self.config.pretty {
16057                self.write_newline();
16058            } else {
16059                self.write_space();
16060            }
16061            self.write_keyword("START WITH");
16062            self.write_space();
16063            self.generate_expression(start)?;
16064        }
16065
16066        // Generate CONNECT BY
16067        if self.config.pretty {
16068            self.write_newline();
16069        } else {
16070            self.write_space();
16071        }
16072        self.write_keyword("CONNECT BY");
16073        if connect.nocycle {
16074            self.write_space();
16075            self.write_keyword("NOCYCLE");
16076        }
16077        self.write_space();
16078        self.generate_expression(&connect.connect)?;
16079
16080        Ok(())
16081    }
16082
16083    /// Generate Connect expression (for Expression::Connect variant)
16084    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
16085        self.generate_connect(connect)
16086    }
16087
16088    /// Generate PRIOR expression
16089    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
16090        self.write_keyword("PRIOR");
16091        self.write_space();
16092        self.generate_expression(&prior.this)?;
16093        Ok(())
16094    }
16095
16096    /// Generate CONNECT_BY_ROOT function
16097    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
16098    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
16099        self.write_keyword("CONNECT_BY_ROOT");
16100        self.write_space();
16101        self.generate_expression(&cbr.this)?;
16102        Ok(())
16103    }
16104
16105    /// Generate MATCH_RECOGNIZE clause
16106    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
16107        use crate::dialects::DialectType;
16108
16109        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
16110        let supports_match_recognize = matches!(
16111            self.config.dialect,
16112            Some(DialectType::Oracle)
16113                | Some(DialectType::Snowflake)
16114                | Some(DialectType::Presto)
16115                | Some(DialectType::Trino)
16116        );
16117
16118        // Generate the source table first
16119        if let Some(source) = &mr.this {
16120            self.generate_expression(source)?;
16121        }
16122
16123        if !supports_match_recognize {
16124            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
16125            return Ok(());
16126        }
16127
16128        // In pretty mode, MATCH_RECOGNIZE should be on a new line
16129        if self.config.pretty {
16130            self.write_newline();
16131        } else {
16132            self.write_space();
16133        }
16134
16135        self.write_keyword("MATCH_RECOGNIZE");
16136        self.write(" (");
16137
16138        if self.config.pretty {
16139            self.indent_level += 1;
16140        }
16141
16142        let mut needs_separator = false;
16143
16144        // PARTITION BY
16145        if let Some(partition_by) = &mr.partition_by {
16146            if !partition_by.is_empty() {
16147                if self.config.pretty {
16148                    self.write_newline();
16149                    self.write_indent();
16150                }
16151                self.write_keyword("PARTITION BY");
16152                self.write_space();
16153                for (i, expr) in partition_by.iter().enumerate() {
16154                    if i > 0 {
16155                        self.write(", ");
16156                    }
16157                    self.generate_expression(expr)?;
16158                }
16159                needs_separator = true;
16160            }
16161        }
16162
16163        // ORDER BY
16164        if let Some(order_by) = &mr.order_by {
16165            if !order_by.is_empty() {
16166                if needs_separator {
16167                    if self.config.pretty {
16168                        self.write_newline();
16169                        self.write_indent();
16170                    } else {
16171                        self.write_space();
16172                    }
16173                } else if self.config.pretty {
16174                    self.write_newline();
16175                    self.write_indent();
16176                }
16177                self.write_keyword("ORDER BY");
16178                // In pretty mode, put each ORDER BY column on a new indented line
16179                if self.config.pretty {
16180                    self.indent_level += 1;
16181                    for (i, ordered) in order_by.iter().enumerate() {
16182                        if i > 0 {
16183                            self.write(",");
16184                        }
16185                        self.write_newline();
16186                        self.write_indent();
16187                        self.generate_ordered(ordered)?;
16188                    }
16189                    self.indent_level -= 1;
16190                } else {
16191                    self.write_space();
16192                    for (i, ordered) in order_by.iter().enumerate() {
16193                        if i > 0 {
16194                            self.write(", ");
16195                        }
16196                        self.generate_ordered(ordered)?;
16197                    }
16198                }
16199                needs_separator = true;
16200            }
16201        }
16202
16203        // MEASURES
16204        if let Some(measures) = &mr.measures {
16205            if !measures.is_empty() {
16206                if needs_separator {
16207                    if self.config.pretty {
16208                        self.write_newline();
16209                        self.write_indent();
16210                    } else {
16211                        self.write_space();
16212                    }
16213                } else if self.config.pretty {
16214                    self.write_newline();
16215                    self.write_indent();
16216                }
16217                self.write_keyword("MEASURES");
16218                // In pretty mode, put each MEASURE on a new indented line
16219                if self.config.pretty {
16220                    self.indent_level += 1;
16221                    for (i, measure) in measures.iter().enumerate() {
16222                        if i > 0 {
16223                            self.write(",");
16224                        }
16225                        self.write_newline();
16226                        self.write_indent();
16227                        // Handle RUNNING/FINAL prefix
16228                        if let Some(semantics) = &measure.window_frame {
16229                            match semantics {
16230                                MatchRecognizeSemantics::Running => {
16231                                    self.write_keyword("RUNNING");
16232                                    self.write_space();
16233                                }
16234                                MatchRecognizeSemantics::Final => {
16235                                    self.write_keyword("FINAL");
16236                                    self.write_space();
16237                                }
16238                            }
16239                        }
16240                        self.generate_expression(&measure.this)?;
16241                    }
16242                    self.indent_level -= 1;
16243                } else {
16244                    self.write_space();
16245                    for (i, measure) in measures.iter().enumerate() {
16246                        if i > 0 {
16247                            self.write(", ");
16248                        }
16249                        // Handle RUNNING/FINAL prefix
16250                        if let Some(semantics) = &measure.window_frame {
16251                            match semantics {
16252                                MatchRecognizeSemantics::Running => {
16253                                    self.write_keyword("RUNNING");
16254                                    self.write_space();
16255                                }
16256                                MatchRecognizeSemantics::Final => {
16257                                    self.write_keyword("FINAL");
16258                                    self.write_space();
16259                                }
16260                            }
16261                        }
16262                        self.generate_expression(&measure.this)?;
16263                    }
16264                }
16265                needs_separator = true;
16266            }
16267        }
16268
16269        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
16270        if let Some(rows) = &mr.rows {
16271            if needs_separator {
16272                if self.config.pretty {
16273                    self.write_newline();
16274                    self.write_indent();
16275                } else {
16276                    self.write_space();
16277                }
16278            } else if self.config.pretty {
16279                self.write_newline();
16280                self.write_indent();
16281            }
16282            match rows {
16283                MatchRecognizeRows::OneRowPerMatch => {
16284                    self.write_keyword("ONE ROW PER MATCH");
16285                }
16286                MatchRecognizeRows::AllRowsPerMatch => {
16287                    self.write_keyword("ALL ROWS PER MATCH");
16288                }
16289                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
16290                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
16291                }
16292                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
16293                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
16294                }
16295                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
16296                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
16297                }
16298            }
16299            needs_separator = true;
16300        }
16301
16302        // AFTER MATCH SKIP
16303        if let Some(after) = &mr.after {
16304            if needs_separator {
16305                if self.config.pretty {
16306                    self.write_newline();
16307                    self.write_indent();
16308                } else {
16309                    self.write_space();
16310                }
16311            } else if self.config.pretty {
16312                self.write_newline();
16313                self.write_indent();
16314            }
16315            match after {
16316                MatchRecognizeAfter::PastLastRow => {
16317                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
16318                }
16319                MatchRecognizeAfter::ToNextRow => {
16320                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
16321                }
16322                MatchRecognizeAfter::ToFirst(ident) => {
16323                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
16324                    self.write_space();
16325                    self.generate_identifier(ident)?;
16326                }
16327                MatchRecognizeAfter::ToLast(ident) => {
16328                    self.write_keyword("AFTER MATCH SKIP TO LAST");
16329                    self.write_space();
16330                    self.generate_identifier(ident)?;
16331                }
16332            }
16333            needs_separator = true;
16334        }
16335
16336        // PATTERN
16337        if let Some(pattern) = &mr.pattern {
16338            if needs_separator {
16339                if self.config.pretty {
16340                    self.write_newline();
16341                    self.write_indent();
16342                } else {
16343                    self.write_space();
16344                }
16345            } else if self.config.pretty {
16346                self.write_newline();
16347                self.write_indent();
16348            }
16349            self.write_keyword("PATTERN");
16350            self.write_space();
16351            self.write("(");
16352            self.write(pattern);
16353            self.write(")");
16354            needs_separator = true;
16355        }
16356
16357        // DEFINE
16358        if let Some(define) = &mr.define {
16359            if !define.is_empty() {
16360                if needs_separator {
16361                    if self.config.pretty {
16362                        self.write_newline();
16363                        self.write_indent();
16364                    } else {
16365                        self.write_space();
16366                    }
16367                } else if self.config.pretty {
16368                    self.write_newline();
16369                    self.write_indent();
16370                }
16371                self.write_keyword("DEFINE");
16372                // In pretty mode, put each DEFINE on a new indented line
16373                if self.config.pretty {
16374                    self.indent_level += 1;
16375                    for (i, (name, expr)) in define.iter().enumerate() {
16376                        if i > 0 {
16377                            self.write(",");
16378                        }
16379                        self.write_newline();
16380                        self.write_indent();
16381                        self.generate_identifier(name)?;
16382                        self.write(" AS ");
16383                        self.generate_expression(expr)?;
16384                    }
16385                    self.indent_level -= 1;
16386                } else {
16387                    self.write_space();
16388                    for (i, (name, expr)) in define.iter().enumerate() {
16389                        if i > 0 {
16390                            self.write(", ");
16391                        }
16392                        self.generate_identifier(name)?;
16393                        self.write(" AS ");
16394                        self.generate_expression(expr)?;
16395                    }
16396                }
16397            }
16398        }
16399
16400        if self.config.pretty {
16401            self.indent_level -= 1;
16402            self.write_newline();
16403        }
16404        self.write(")");
16405
16406        // Alias - only include AS if it was explicitly present in the input
16407        if let Some(alias) = &mr.alias {
16408            self.write(" ");
16409            if mr.alias_explicit_as {
16410                self.write_keyword("AS");
16411                self.write(" ");
16412            }
16413            self.generate_identifier(alias)?;
16414        }
16415
16416        Ok(())
16417    }
16418
16419    /// Generate a query hint /*+ ... */
16420    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
16421        use crate::dialects::DialectType;
16422
16423        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
16424        let supports_hints = matches!(
16425            self.config.dialect,
16426            None |  // No dialect = preserve everything
16427            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
16428            Some(DialectType::Spark) | Some(DialectType::Hive) |
16429            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
16430        );
16431
16432        if !supports_hints || hint.expressions.is_empty() {
16433            return Ok(());
16434        }
16435
16436        // First, expand raw hint text into individual hint strings
16437        // This handles the case where the parser stored multiple hints as a single raw string
16438        let mut hint_strings: Vec<String> = Vec::new();
16439        for expr in &hint.expressions {
16440            match expr {
16441                HintExpression::Raw(text) => {
16442                    // Parse raw hint text into individual hint function calls
16443                    let parsed = self.parse_raw_hint_text(text);
16444                    hint_strings.extend(parsed);
16445                }
16446                _ => {
16447                    hint_strings.push(self.hint_expression_to_string(expr)?);
16448                }
16449            }
16450        }
16451
16452        // In pretty mode with multiple hints, always use multiline format
16453        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
16454        // always joins with newlines in pretty mode
16455        let use_multiline = self.config.pretty && hint_strings.len() > 1;
16456
16457        if use_multiline {
16458            // Pretty print with each hint on its own line
16459            self.write(" /*+ ");
16460            for (i, hint_str) in hint_strings.iter().enumerate() {
16461                if i > 0 {
16462                    self.write_newline();
16463                    self.write("  "); // 2-space indent within hint block
16464                }
16465                self.write(hint_str);
16466            }
16467            self.write(" */");
16468        } else {
16469            // Single line format
16470            self.write(" /*+ ");
16471            let sep = match self.config.dialect {
16472                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
16473                _ => " ",
16474            };
16475            for (i, hint_str) in hint_strings.iter().enumerate() {
16476                if i > 0 {
16477                    self.write(sep);
16478                }
16479                self.write(hint_str);
16480            }
16481            self.write(" */");
16482        }
16483
16484        Ok(())
16485    }
16486
16487    /// Parse raw hint text into individual hint function calls
16488    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
16489    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
16490    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
16491        let mut results = Vec::new();
16492        let mut chars = text.chars().peekable();
16493        let mut current = String::new();
16494        let mut paren_depth = 0;
16495        let mut has_unparseable_content = false;
16496        let mut position_after_last_function = 0;
16497        let mut char_position = 0;
16498
16499        while let Some(c) = chars.next() {
16500            char_position += c.len_utf8();
16501            match c {
16502                '(' => {
16503                    paren_depth += 1;
16504                    current.push(c);
16505                }
16506                ')' => {
16507                    paren_depth -= 1;
16508                    current.push(c);
16509                    // When we close the outer parenthesis, we've completed a hint function
16510                    if paren_depth == 0 {
16511                        let trimmed = current.trim().to_string();
16512                        if !trimmed.is_empty() {
16513                            // Format this hint for pretty printing if needed
16514                            let formatted = self.format_hint_function(&trimmed);
16515                            results.push(formatted);
16516                        }
16517                        current.clear();
16518                        position_after_last_function = char_position;
16519                    }
16520                }
16521                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
16522                    // Space/comma/whitespace outside parentheses - skip
16523                }
16524                _ if paren_depth == 0 => {
16525                    // Character outside parentheses - accumulate for potential hint name
16526                    current.push(c);
16527                }
16528                _ => {
16529                    current.push(c);
16530                }
16531            }
16532        }
16533
16534        // Check if there's remaining text after the last function call
16535        let remaining_text = text[position_after_last_function..].trim();
16536        if !remaining_text.is_empty() {
16537            // Check if it looks like valid hint function names
16538            // Valid hint identifiers typically are uppercase alphanumeric with underscores
16539            // If we see multiple words without parens, it's likely unparseable
16540            let words: Vec<&str> = remaining_text.split_whitespace().collect();
16541            let looks_like_hint_functions = words.iter().all(|word| {
16542                // A valid hint name followed by opening paren, or a standalone uppercase identifier
16543                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
16544            });
16545
16546            if !looks_like_hint_functions && words.len() > 1 {
16547                has_unparseable_content = true;
16548            }
16549        }
16550
16551        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
16552        if has_unparseable_content {
16553            return vec![text.trim().to_string()];
16554        }
16555
16556        // If we couldn't parse anything, return the original text as a single hint
16557        if results.is_empty() {
16558            results.push(text.trim().to_string());
16559        }
16560
16561        results
16562    }
16563
16564    /// Format a hint function for pretty printing
16565    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
16566    fn format_hint_function(&self, hint: &str) -> String {
16567        if !self.config.pretty {
16568            return hint.to_string();
16569        }
16570
16571        // Try to parse NAME(args) pattern
16572        if let Some(paren_pos) = hint.find('(') {
16573            if hint.ends_with(')') {
16574                let name = &hint[..paren_pos];
16575                let args_str = &hint[paren_pos + 1..hint.len() - 1];
16576
16577                // Parse arguments (space-separated for Oracle hints)
16578                let args: Vec<&str> = args_str.split_whitespace().collect();
16579
16580                // Calculate total width of arguments
16581                let total_args_width: usize =
16582                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
16583
16584                // If too wide, format on multiple lines
16585                if total_args_width > self.config.max_text_width && !args.is_empty() {
16586                    let mut result = format!("{}(\n", name);
16587                    for arg in &args {
16588                        result.push_str("    "); // 4-space indent for args
16589                        result.push_str(arg);
16590                        result.push('\n');
16591                    }
16592                    result.push_str("  )"); // 2-space indent for closing paren
16593                    return result;
16594                }
16595            }
16596        }
16597
16598        hint.to_string()
16599    }
16600
16601    /// Convert a hint expression to a string, handling multiline formatting for long arguments
16602    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
16603        match expr {
16604            HintExpression::Function { name, args } => {
16605                // Generate each argument to a string
16606                let arg_strings: Vec<String> = args
16607                    .iter()
16608                    .map(|arg| {
16609                        let mut gen = Generator::with_arc_config(self.config.clone());
16610                        gen.generate_expression(arg)?;
16611                        Ok(gen.output)
16612                    })
16613                    .collect::<Result<Vec<_>>>()?;
16614
16615                // Oracle hints use space-separated arguments, not comma-separated
16616                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
16617                    + arg_strings.len().saturating_sub(1); // spaces between args
16618
16619                // Check if function args need multiline formatting
16620                // Use too_wide check for argument formatting
16621                let args_multiline =
16622                    self.config.pretty && total_args_width > self.config.max_text_width;
16623
16624                if args_multiline && !arg_strings.is_empty() {
16625                    // Multiline format for long argument lists
16626                    let mut result = format!("{}(\n", name);
16627                    for arg_str in &arg_strings {
16628                        result.push_str("    "); // 4-space indent for args
16629                        result.push_str(arg_str);
16630                        result.push('\n');
16631                    }
16632                    result.push_str("  )"); // 2-space indent for closing paren
16633                    Ok(result)
16634                } else {
16635                    // Single line format with space-separated args (Oracle style)
16636                    let args_str = arg_strings.join(" ");
16637                    Ok(format!("{}({})", name, args_str))
16638                }
16639            }
16640            HintExpression::Identifier(name) => Ok(name.clone()),
16641            HintExpression::Raw(text) => {
16642                // For pretty printing, try to format the raw text
16643                if self.config.pretty {
16644                    Ok(self.format_hint_function(text))
16645                } else {
16646                    Ok(text.clone())
16647                }
16648            }
16649        }
16650    }
16651
16652    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
16653        // PostgreSQL ONLY modifier: prevents scanning child tables
16654        if table.only {
16655            self.write_keyword("ONLY");
16656            self.write_space();
16657        }
16658
16659        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
16660        if let Some(ref identifier_func) = table.identifier_func {
16661            self.generate_expression(identifier_func)?;
16662            // If table name parts are present, emit .catalog.schema.name after the function
16663            if !table.name.name.is_empty() {
16664                if let Some(catalog) = &table.catalog {
16665                    self.write(".");
16666                    self.generate_identifier(catalog)?;
16667                }
16668                if let Some(schema) = &table.schema {
16669                    self.write(".");
16670                    self.generate_identifier(schema)?;
16671                }
16672                self.write(".");
16673                self.generate_identifier(&table.name)?;
16674            }
16675        } else {
16676            if let Some(catalog) = &table.catalog {
16677                self.generate_identifier(catalog)?;
16678                self.write(".");
16679            }
16680            if let Some(schema) = &table.schema {
16681                self.generate_identifier(schema)?;
16682                self.write(".");
16683            }
16684            self.generate_identifier(&table.name)?;
16685        }
16686
16687        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
16688        if let Some(changes) = &table.changes {
16689            self.write(" ");
16690            self.generate_changes(changes)?;
16691        }
16692
16693        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
16694        if !table.partitions.is_empty() {
16695            self.write_space();
16696            self.write_keyword("PARTITION");
16697            self.write("(");
16698            for (i, partition) in table.partitions.iter().enumerate() {
16699                if i > 0 {
16700                    self.write(", ");
16701                }
16702                self.generate_identifier(partition)?;
16703            }
16704            self.write(")");
16705        }
16706
16707        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
16708        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
16709        if table.changes.is_none() {
16710            if let Some(when) = &table.when {
16711                self.write_space();
16712                self.generate_historical_data(when)?;
16713            }
16714        }
16715
16716        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
16717        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
16718        if !system_time_post_alias {
16719            if let Some(ref system_time) = table.system_time {
16720                self.write_space();
16721                self.write(system_time);
16722            }
16723        }
16724
16725        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
16726        if let Some(ref version) = table.version {
16727            self.write_space();
16728            self.generate_version(version)?;
16729        }
16730
16731        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
16732        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
16733        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
16734        let alias_post_tablesample = self.config.alias_post_tablesample;
16735
16736        if alias_post_tablesample {
16737            // TABLESAMPLE before alias (Oracle, Hive, Spark)
16738            self.generate_table_sample_clause(table)?;
16739        }
16740
16741        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
16742        // For SQLite, INDEXED BY hints come after the alias, so skip here
16743        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
16744            && table.hints.iter().any(|h| {
16745                if let Expression::Identifier(id) = h {
16746                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
16747                } else {
16748                    false
16749                }
16750            });
16751        if !table.hints.is_empty() && !is_sqlite_hint {
16752            for hint in &table.hints {
16753                self.write_space();
16754                self.generate_expression(hint)?;
16755            }
16756        }
16757
16758        if let Some(alias) = &table.alias {
16759            self.write_space();
16760            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
16761            // Generic mode and most dialects always use AS for table aliases
16762            let always_use_as = self.config.dialect.is_none()
16763                || matches!(
16764                    self.config.dialect,
16765                    Some(DialectType::Generic)
16766                        | Some(DialectType::PostgreSQL)
16767                        | Some(DialectType::Redshift)
16768                        | Some(DialectType::Snowflake)
16769                        | Some(DialectType::BigQuery)
16770                        | Some(DialectType::DuckDB)
16771                        | Some(DialectType::Presto)
16772                        | Some(DialectType::Trino)
16773                        | Some(DialectType::TSQL)
16774                        | Some(DialectType::Fabric)
16775                        | Some(DialectType::MySQL)
16776                        | Some(DialectType::Spark)
16777                        | Some(DialectType::Hive)
16778                        | Some(DialectType::SQLite)
16779                        | Some(DialectType::Drill)
16780                );
16781            let is_stage_ref = table.name.name.starts_with('@');
16782            // Oracle never uses AS for table aliases
16783            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16784            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
16785                self.write_keyword("AS");
16786                self.write_space();
16787            }
16788            self.generate_identifier(alias)?;
16789
16790            // Output column aliases if present: AS t(c1, c2)
16791            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
16792            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
16793                self.write("(");
16794                for (i, col_alias) in table.column_aliases.iter().enumerate() {
16795                    if i > 0 {
16796                        self.write(", ");
16797                    }
16798                    self.generate_identifier(col_alias)?;
16799                }
16800                self.write(")");
16801            }
16802        }
16803
16804        // BigQuery: FOR SYSTEM_TIME AS OF after alias
16805        if system_time_post_alias {
16806            if let Some(ref system_time) = table.system_time {
16807                self.write_space();
16808                self.write(system_time);
16809            }
16810        }
16811
16812        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
16813        if !alias_post_tablesample {
16814            self.generate_table_sample_clause(table)?;
16815        }
16816
16817        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
16818        if is_sqlite_hint {
16819            for hint in &table.hints {
16820                self.write_space();
16821                self.generate_expression(hint)?;
16822            }
16823        }
16824
16825        // ClickHouse FINAL modifier
16826        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
16827            self.write_space();
16828            self.write_keyword("FINAL");
16829        }
16830
16831        // Output trailing comments
16832        for comment in &table.trailing_comments {
16833            self.write_space();
16834            self.write_formatted_comment(comment);
16835        }
16836        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
16837        // output here - they are output by the FROM/PIVOT generator after the full expression
16838
16839        Ok(())
16840    }
16841
16842    /// Helper to output TABLESAMPLE clause for a table reference
16843    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
16844        if let Some(ref ts) = table.table_sample {
16845            self.write_space();
16846            if ts.is_using_sample {
16847                self.write_keyword("USING SAMPLE");
16848            } else {
16849                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
16850                self.write_keyword(self.config.tablesample_keywords);
16851            }
16852            self.generate_sample_body(ts)?;
16853            // Seed for table-level sample - use dialect's configured keyword
16854            if let Some(ref seed) = ts.seed {
16855                self.write_space();
16856                self.write_keyword(self.config.tablesample_seed_keyword);
16857                self.write(" (");
16858                self.generate_expression(seed)?;
16859                self.write(")");
16860            }
16861        }
16862        Ok(())
16863    }
16864
16865    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
16866        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
16867        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
16868
16869        if sr.quoted {
16870            self.write("'");
16871        }
16872
16873        self.write(&sr.name);
16874        if let Some(path) = &sr.path {
16875            self.write(path);
16876        }
16877
16878        if sr.quoted {
16879            self.write("'");
16880        }
16881
16882        // Output FILE_FORMAT and PATTERN if present
16883        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
16884        if has_options {
16885            self.write(" (");
16886            let mut first = true;
16887
16888            if let Some(file_format) = &sr.file_format {
16889                if !first {
16890                    self.write(", ");
16891                }
16892                self.write_keyword("FILE_FORMAT");
16893                self.write(" => ");
16894                self.generate_expression(file_format)?;
16895                first = false;
16896            }
16897
16898            if let Some(pattern) = &sr.pattern {
16899                if !first {
16900                    self.write(", ");
16901                }
16902                self.write_keyword("PATTERN");
16903                self.write(" => '");
16904                self.write(pattern);
16905                self.write("'");
16906            }
16907
16908            self.write(")");
16909        }
16910        Ok(())
16911    }
16912
16913    fn generate_star(&mut self, star: &Star) -> Result<()> {
16914        use crate::dialects::DialectType;
16915
16916        if let Some(table) = &star.table {
16917            self.generate_identifier(table)?;
16918            self.write(".");
16919        }
16920        self.write("*");
16921
16922        // Generate EXCLUDE/EXCEPT clause based on dialect
16923        if let Some(except) = &star.except {
16924            if !except.is_empty() {
16925                self.write_space();
16926                // Use dialect-appropriate keyword
16927                match self.config.dialect {
16928                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
16929                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
16930                        self.write_keyword("EXCLUDE")
16931                    }
16932                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
16933                }
16934                self.write(" (");
16935                for (i, col) in except.iter().enumerate() {
16936                    if i > 0 {
16937                        self.write(", ");
16938                    }
16939                    self.generate_identifier(col)?;
16940                }
16941                self.write(")");
16942            }
16943        }
16944
16945        // Generate REPLACE clause
16946        if let Some(replace) = &star.replace {
16947            if !replace.is_empty() {
16948                self.write_space();
16949                self.write_keyword("REPLACE");
16950                self.write(" (");
16951                for (i, alias) in replace.iter().enumerate() {
16952                    if i > 0 {
16953                        self.write(", ");
16954                    }
16955                    self.generate_expression(&alias.this)?;
16956                    self.write_space();
16957                    self.write_keyword("AS");
16958                    self.write_space();
16959                    self.generate_identifier(&alias.alias)?;
16960                }
16961                self.write(")");
16962            }
16963        }
16964
16965        // Generate RENAME clause (Snowflake specific)
16966        if let Some(rename) = &star.rename {
16967            if !rename.is_empty() {
16968                self.write_space();
16969                self.write_keyword("RENAME");
16970                self.write(" (");
16971                for (i, (old_name, new_name)) in rename.iter().enumerate() {
16972                    if i > 0 {
16973                        self.write(", ");
16974                    }
16975                    self.generate_identifier(old_name)?;
16976                    self.write_space();
16977                    self.write_keyword("AS");
16978                    self.write_space();
16979                    self.generate_identifier(new_name)?;
16980                }
16981                self.write(")");
16982            }
16983        }
16984
16985        // Output trailing comments
16986        for comment in &star.trailing_comments {
16987            self.write_space();
16988            self.write_formatted_comment(comment);
16989        }
16990
16991        Ok(())
16992    }
16993
16994    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
16995    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
16996        self.write("{");
16997        match expr {
16998            Expression::Star(star) => {
16999                // Generate the star (table.* or just * with optional EXCLUDE)
17000                self.generate_star(star)?;
17001            }
17002            Expression::ILike(ilike) => {
17003                // {* ILIKE 'pattern'} syntax
17004                self.generate_expression(&ilike.left)?;
17005                self.write_space();
17006                self.write_keyword("ILIKE");
17007                self.write_space();
17008                self.generate_expression(&ilike.right)?;
17009            }
17010            _ => {
17011                self.generate_expression(expr)?;
17012            }
17013        }
17014        self.write("}");
17015        Ok(())
17016    }
17017
17018    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
17019        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
17020        // to avoid duplication (comments are captured as both Column.trailing_comments
17021        // and Alias.pre_alias_comments during parsing)
17022        match &alias.this {
17023            Expression::Column(col) => {
17024                // Generate column without trailing comments - they're in pre_alias_comments
17025                if let Some(table) = &col.table {
17026                    self.generate_identifier(table)?;
17027                    self.write(".");
17028                }
17029                self.generate_identifier(&col.name)?;
17030            }
17031            _ => {
17032                self.generate_expression(&alias.this)?;
17033            }
17034        }
17035
17036        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
17037        // moves pre-alias comments to after the alias. When there are also trailing_comments,
17038        // keep pre-alias comments in their original position (between expression and AS).
17039        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
17040            for comment in &alias.pre_alias_comments {
17041                self.write_space();
17042                self.write_formatted_comment(comment);
17043            }
17044        }
17045
17046        use crate::dialects::DialectType;
17047
17048        // Determine if we should skip AS keyword for table-valued function aliases
17049        // Oracle and some other dialects don't use AS for table aliases
17050        // Note: We specifically use TableFromRows here, NOT Function, because Function
17051        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
17052        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
17053        let is_table_source = matches!(
17054            &alias.this,
17055            Expression::JSONTable(_)
17056                | Expression::XMLTable(_)
17057                | Expression::TableFromRows(_)
17058                | Expression::Unnest(_)
17059                | Expression::MatchRecognize(_)
17060                | Expression::Select(_)
17061                | Expression::Subquery(_)
17062                | Expression::Paren(_)
17063        );
17064        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
17065        let skip_as = is_table_source && dialect_skips_table_alias_as;
17066
17067        self.write_space();
17068        if !skip_as {
17069            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
17070                if let Some(ref alias_keyword) = alias.alias_keyword {
17071                    self.write(alias_keyword);
17072                } else {
17073                    self.write_keyword("AS");
17074                }
17075            } else {
17076                self.write_keyword("AS");
17077            }
17078            self.write_space();
17079        }
17080
17081        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
17082        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
17083
17084        // Check if we have column aliases only (no table alias name)
17085        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
17086            // Generate AS (col1, col2, ...)
17087            self.write("(");
17088            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
17089                if i > 0 {
17090                    self.write(", ");
17091                }
17092                self.generate_alias_identifier(col_alias)?;
17093            }
17094            self.write(")");
17095        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
17096            // Generate AS alias(col1, col2, ...)
17097            self.generate_alias_identifier(&alias.alias)?;
17098            self.write("(");
17099            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
17100                if i > 0 {
17101                    self.write(", ");
17102                }
17103                self.generate_alias_identifier(col_alias)?;
17104            }
17105            self.write(")");
17106        } else {
17107            // Simple alias (or BigQuery without column aliases)
17108            self.generate_alias_identifier(&alias.alias)?;
17109        }
17110
17111        // Output trailing comments (comments after the alias)
17112        for comment in &alias.trailing_comments {
17113            self.write_space();
17114            self.write_formatted_comment(comment);
17115        }
17116
17117        // Output pre-alias comments: when there are no trailing_comments, sqlglot
17118        // moves pre-alias comments to after the alias. When there are trailing_comments,
17119        // the pre-alias comments were already lost (consumed as column trailing comments
17120        // that were then used as pre_alias_comments). We always emit them after alias.
17121        if alias.trailing_comments.is_empty() {
17122            for comment in &alias.pre_alias_comments {
17123                self.write_space();
17124                self.write_formatted_comment(comment);
17125            }
17126        }
17127
17128        Ok(())
17129    }
17130
17131    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
17132        use crate::dialects::DialectType;
17133
17134        // SingleStore uses :> syntax
17135        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
17136            self.generate_expression(&cast.this)?;
17137            self.write(" :> ");
17138            self.generate_data_type(&cast.to)?;
17139            return Ok(());
17140        }
17141
17142        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
17143        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
17144            let is_unknown_type = matches!(cast.to, DataType::Unknown)
17145                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
17146            if is_unknown_type {
17147                if let Some(format) = &cast.format {
17148                    self.write_keyword("CAST");
17149                    self.write("(");
17150                    self.generate_expression(&cast.this)?;
17151                    self.write_space();
17152                    self.write_keyword("AS");
17153                    self.write_space();
17154                    self.write_keyword("FORMAT");
17155                    self.write_space();
17156                    self.generate_expression(format)?;
17157                    self.write(")");
17158                    return Ok(());
17159                }
17160            }
17161        }
17162
17163        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
17164        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
17165        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
17166            if let Some(format) = &cast.format {
17167                // Check if target type is DATE or TIMESTAMP
17168                let is_date = matches!(cast.to, DataType::Date);
17169                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
17170
17171                if is_date || is_timestamp {
17172                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
17173                    self.write_keyword(func_name);
17174                    self.write("(");
17175                    self.generate_expression(&cast.this)?;
17176                    self.write(", ");
17177
17178                    // Normalize format string for Oracle (HH -> HH12)
17179                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
17180                    if let Expression::Literal(lit) = format.as_ref() {
17181                        if let Literal::String(fmt_str) = lit.as_ref() {
17182                            let normalized = self.normalize_oracle_format(fmt_str);
17183                            self.write("'");
17184                            self.write(&normalized);
17185                            self.write("'");
17186                        }
17187                    } else {
17188                        self.generate_expression(format)?;
17189                    }
17190
17191                    self.write(")");
17192                    return Ok(());
17193                }
17194            }
17195        }
17196
17197        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
17198        // This preserves sqlglot's typed inline array literal output.
17199        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
17200            if let Expression::Array(arr) = &cast.this {
17201                self.generate_data_type(&cast.to)?;
17202                // Output just the bracket content [values] without the ARRAY prefix
17203                self.write("[");
17204                for (i, expr) in arr.expressions.iter().enumerate() {
17205                    if i > 0 {
17206                        self.write(", ");
17207                    }
17208                    self.generate_expression(expr)?;
17209                }
17210                self.write("]");
17211                return Ok(());
17212            }
17213            if matches!(&cast.this, Expression::ArrayFunc(_)) {
17214                self.generate_data_type(&cast.to)?;
17215                self.generate_expression(&cast.this)?;
17216                return Ok(());
17217            }
17218        }
17219
17220        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
17221        // convert the inner Struct to ROW(values...) format
17222        if matches!(
17223            self.config.dialect,
17224            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
17225        ) {
17226            if let Expression::Struct(ref s) = cast.this {
17227                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
17228                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
17229                    self.write_keyword("CAST");
17230                    self.write("(");
17231                    self.generate_struct_as_row(s)?;
17232                    self.write_space();
17233                    self.write_keyword("AS");
17234                    self.write_space();
17235                    self.generate_data_type(&cast.to)?;
17236                    self.write(")");
17237                    return Ok(());
17238                }
17239            }
17240        }
17241
17242        // Determine if we should use :: syntax based on dialect
17243        // PostgreSQL prefers :: for identity, most others prefer CAST()
17244        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
17245
17246        if use_double_colon {
17247            // PostgreSQL :: syntax: expr::type
17248            self.generate_expression(&cast.this)?;
17249            self.write("::");
17250            self.generate_data_type(&cast.to)?;
17251        } else {
17252            // Standard CAST() syntax
17253            self.write_keyword("CAST");
17254            self.write("(");
17255            self.generate_expression(&cast.this)?;
17256            self.write_space();
17257            self.write_keyword("AS");
17258            self.write_space();
17259            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
17260            // This matches Python sqlglot's CAST_MAPPING behavior
17261            if matches!(
17262                self.config.dialect,
17263                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
17264            ) {
17265                match &cast.to {
17266                    DataType::Custom { ref name } => {
17267                        if name.eq_ignore_ascii_case("LONGTEXT")
17268                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
17269                            || name.eq_ignore_ascii_case("TINYTEXT")
17270                            || name.eq_ignore_ascii_case("LONGBLOB")
17271                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
17272                            || name.eq_ignore_ascii_case("TINYBLOB")
17273                        {
17274                            self.write_keyword("CHAR");
17275                        } else {
17276                            self.generate_data_type(&cast.to)?;
17277                        }
17278                    }
17279                    DataType::VarChar { length, .. } => {
17280                        // MySQL CAST: VARCHAR -> CHAR
17281                        self.write_keyword("CHAR");
17282                        if let Some(n) = length {
17283                            self.write(&format!("({})", n));
17284                        }
17285                    }
17286                    DataType::Text => {
17287                        // MySQL CAST: TEXT -> CHAR
17288                        self.write_keyword("CHAR");
17289                    }
17290                    DataType::Timestamp {
17291                        precision,
17292                        timezone: false,
17293                    } => {
17294                        // MySQL CAST: TIMESTAMP -> DATETIME
17295                        self.write_keyword("DATETIME");
17296                        if let Some(p) = precision {
17297                            self.write(&format!("({})", p));
17298                        }
17299                    }
17300                    _ => {
17301                        self.generate_data_type(&cast.to)?;
17302                    }
17303                }
17304            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
17305                // Snowflake CAST: STRING -> VARCHAR
17306                match &cast.to {
17307                    DataType::String { length } => {
17308                        self.write_keyword("VARCHAR");
17309                        if let Some(n) = length {
17310                            self.write(&format!("({})", n));
17311                        }
17312                    }
17313                    _ => {
17314                        self.generate_data_type(&cast.to)?;
17315                    }
17316                }
17317            } else {
17318                self.generate_data_type(&cast.to)?;
17319            }
17320
17321            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
17322            if let Some(default) = &cast.default {
17323                self.write_space();
17324                self.write_keyword("DEFAULT");
17325                self.write_space();
17326                self.generate_expression(default)?;
17327                self.write_space();
17328                self.write_keyword("ON");
17329                self.write_space();
17330                self.write_keyword("CONVERSION");
17331                self.write_space();
17332                self.write_keyword("ERROR");
17333            }
17334
17335            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
17336            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
17337            if let Some(format) = &cast.format {
17338                // Check if Oracle dialect - use comma syntax
17339                if matches!(
17340                    self.config.dialect,
17341                    Some(crate::dialects::DialectType::Oracle)
17342                ) {
17343                    self.write(", ");
17344                } else {
17345                    self.write_space();
17346                    self.write_keyword("FORMAT");
17347                    self.write_space();
17348                }
17349                self.generate_expression(format)?;
17350            }
17351
17352            self.write(")");
17353            // Output trailing comments
17354            for comment in &cast.trailing_comments {
17355                self.write_space();
17356                self.write_formatted_comment(comment);
17357            }
17358        }
17359        Ok(())
17360    }
17361
17362    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
17363    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
17364    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
17365        self.write_keyword("ROW");
17366        self.write("(");
17367        for (i, (_, expr)) in s.fields.iter().enumerate() {
17368            if i > 0 {
17369                self.write(", ");
17370            }
17371            // Recursively convert inner Struct to ROW format
17372            if let Expression::Struct(ref inner_s) = expr {
17373                self.generate_struct_as_row(inner_s)?;
17374            } else {
17375                self.generate_expression(expr)?;
17376            }
17377        }
17378        self.write(")");
17379        Ok(())
17380    }
17381
17382    /// Normalize Oracle date/time format strings
17383    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
17384    fn normalize_oracle_format(&self, format: &str) -> String {
17385        // Replace standalone HH with HH12 (but not HH12 or HH24)
17386        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
17387        let mut result = String::new();
17388        let chars: Vec<char> = format.chars().collect();
17389        let mut i = 0;
17390
17391        while i < chars.len() {
17392            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
17393                // Check what follows HH
17394                if i + 2 < chars.len() {
17395                    let next = chars[i + 2];
17396                    if next == '1' || next == '2' {
17397                        // This is HH12 or HH24, keep as is
17398                        result.push('H');
17399                        result.push('H');
17400                        i += 2;
17401                        continue;
17402                    }
17403                }
17404                // Standalone HH -> HH12
17405                result.push_str("HH12");
17406                i += 2;
17407            } else {
17408                result.push(chars[i]);
17409                i += 1;
17410            }
17411        }
17412
17413        result
17414    }
17415
17416    /// Check if the current dialect prefers :: cast syntax
17417    /// Preserve ClickHouse's native `::` shorthand when the parser saw it.
17418    fn dialect_prefers_double_colon(&self) -> bool {
17419        matches!(self.config.dialect, Some(DialectType::ClickHouse))
17420    }
17421
17422    /// Generate MOD function - uses % operator for dialects that prefer or require it.
17423    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17424        use crate::dialects::DialectType;
17425
17426        // Several dialects prefer or require x % y instead of MOD(x, y).
17427        let use_percent_operator = matches!(
17428            self.config.dialect,
17429            Some(DialectType::Snowflake)
17430                | Some(DialectType::MySQL)
17431                | Some(DialectType::Presto)
17432                | Some(DialectType::Trino)
17433                | Some(DialectType::PostgreSQL)
17434                | Some(DialectType::DuckDB)
17435                | Some(DialectType::Hive)
17436                | Some(DialectType::Spark)
17437                | Some(DialectType::Databricks)
17438                | Some(DialectType::Athena)
17439                | Some(DialectType::TSQL)
17440                | Some(DialectType::Fabric)
17441        );
17442
17443        if use_percent_operator {
17444            // MOD(a, b) treats both arguments as grouped expressions. When
17445            // lowering to an infix operator, keep binary arguments grouped.
17446            let needs_paren = |e: &Expression| {
17447                matches!(
17448                    e,
17449                    Expression::Add(_)
17450                        | Expression::Sub(_)
17451                        | Expression::Mul(_)
17452                        | Expression::Div(_)
17453                        | Expression::Mod(_)
17454                        | Expression::ModFunc(_)
17455                )
17456            };
17457            if needs_paren(&f.this) {
17458                self.write("(");
17459                self.generate_expression(&f.this)?;
17460                self.write(")");
17461            } else {
17462                self.generate_expression(&f.this)?;
17463            }
17464            self.write(" % ");
17465            if needs_paren(&f.expression) {
17466                self.write("(");
17467                self.generate_expression(&f.expression)?;
17468                self.write(")");
17469            } else {
17470                self.generate_expression(&f.expression)?;
17471            }
17472            Ok(())
17473        } else {
17474            self.generate_binary_func("MOD", &f.this, &f.expression)
17475        }
17476    }
17477
17478    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
17479    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17480        use crate::dialects::DialectType;
17481
17482        // Snowflake normalizes IFNULL to COALESCE
17483        let func_name = match self.config.dialect {
17484            Some(DialectType::Snowflake) => "COALESCE",
17485            _ => "IFNULL",
17486        };
17487
17488        self.generate_binary_func(func_name, &f.this, &f.expression)
17489    }
17490
17491    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
17492    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17493        // Use original function name if preserved (for identity tests)
17494        if let Some(ref original_name) = f.original_name {
17495            return self.generate_binary_func(original_name, &f.this, &f.expression);
17496        }
17497
17498        // Otherwise, use dialect-specific function names
17499        use crate::dialects::DialectType;
17500        let func_name = match self.config.dialect {
17501            Some(DialectType::Snowflake)
17502            | Some(DialectType::ClickHouse)
17503            | Some(DialectType::PostgreSQL)
17504            | Some(DialectType::Presto)
17505            | Some(DialectType::Trino)
17506            | Some(DialectType::Athena)
17507            | Some(DialectType::DuckDB)
17508            | Some(DialectType::BigQuery)
17509            | Some(DialectType::Spark)
17510            | Some(DialectType::Databricks)
17511            | Some(DialectType::Hive) => "COALESCE",
17512            Some(DialectType::MySQL)
17513            | Some(DialectType::Doris)
17514            | Some(DialectType::StarRocks)
17515            | Some(DialectType::SingleStore)
17516            | Some(DialectType::TiDB) => "IFNULL",
17517            _ => "NVL",
17518        };
17519
17520        self.generate_binary_func(func_name, &f.this, &f.expression)
17521    }
17522
17523    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
17524    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
17525        use crate::dialects::DialectType;
17526
17527        // Snowflake normalizes STDDEV_SAMP to STDDEV
17528        let func_name = match self.config.dialect {
17529            Some(DialectType::Snowflake) => "STDDEV",
17530            _ => "STDDEV_SAMP",
17531        };
17532
17533        self.generate_agg_func(func_name, f)
17534    }
17535
17536    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
17537        self.generate_expression(&coll.this)?;
17538        self.write_space();
17539        self.write_keyword("COLLATE");
17540        self.write_space();
17541        if coll.quoted {
17542            // Single-quoted string: COLLATE 'de_DE'
17543            self.write("'");
17544            self.write(&coll.collation);
17545            self.write("'");
17546        } else if coll.double_quoted {
17547            // Double-quoted identifier: COLLATE "de_DE"
17548            self.write("\"");
17549            self.write(&coll.collation);
17550            self.write("\"");
17551        } else {
17552            // Unquoted identifier: COLLATE de_DE
17553            self.write(&coll.collation);
17554        }
17555        Ok(())
17556    }
17557
17558    fn generate_case(&mut self, case: &Case) -> Result<()> {
17559        // In pretty mode, decide whether to expand based on total text width
17560        let multiline_case = if self.config.pretty {
17561            // Build the flat representation to check width
17562            let mut statements: Vec<String> = Vec::new();
17563            let operand_str = if let Some(operand) = &case.operand {
17564                let s = self.generate_to_string(operand)?;
17565                statements.push(format!("CASE {}", s));
17566                s
17567            } else {
17568                statements.push("CASE".to_string());
17569                String::new()
17570            };
17571            let _ = operand_str;
17572            for (condition, result) in &case.whens {
17573                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
17574                statements.push(format!("THEN {}", self.generate_to_string(result)?));
17575            }
17576            if let Some(else_) = &case.else_ {
17577                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
17578            }
17579            statements.push("END".to_string());
17580            self.too_wide(&statements)
17581        } else {
17582            false
17583        };
17584
17585        self.write_keyword("CASE");
17586        if let Some(operand) = &case.operand {
17587            self.write_space();
17588            self.generate_expression(operand)?;
17589        }
17590        if multiline_case {
17591            self.indent_level += 1;
17592        }
17593        for (condition, result) in &case.whens {
17594            if multiline_case {
17595                self.write_newline();
17596                self.write_indent();
17597            } else {
17598                self.write_space();
17599            }
17600            self.write_keyword("WHEN");
17601            self.write_space();
17602            self.generate_expression(condition)?;
17603            if multiline_case {
17604                self.write_newline();
17605                self.write_indent();
17606            } else {
17607                self.write_space();
17608            }
17609            self.write_keyword("THEN");
17610            self.write_space();
17611            self.generate_expression(result)?;
17612        }
17613        if let Some(else_) = &case.else_ {
17614            if multiline_case {
17615                self.write_newline();
17616                self.write_indent();
17617            } else {
17618                self.write_space();
17619            }
17620            self.write_keyword("ELSE");
17621            self.write_space();
17622            self.generate_expression(else_)?;
17623        }
17624        if multiline_case {
17625            self.indent_level -= 1;
17626            self.write_newline();
17627            self.write_indent();
17628        } else {
17629            self.write_space();
17630        }
17631        self.write_keyword("END");
17632        // Emit any comments that were attached to the CASE keyword
17633        for comment in &case.comments {
17634            self.write(" ");
17635            self.write_formatted_comment(comment);
17636        }
17637        Ok(())
17638    }
17639
17640    fn generate_function(&mut self, func: &Function) -> Result<()> {
17641        // Normalize function name based on dialect settings
17642        let normalized_name = if matches!(self.config.dialect, Some(DialectType::Snowflake))
17643            && func.name.to_ascii_uppercase().starts_with("IDENTIFIER(")
17644        {
17645            Cow::Borrowed(func.name.as_str())
17646        } else {
17647            self.normalize_func_name(&func.name)
17648        };
17649
17650        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
17651        if matches!(self.config.dialect, Some(DialectType::DuckDB))
17652            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
17653        {
17654            self.write("LIST_FILTER(");
17655            self.write("[");
17656            for (i, arg) in func.args.iter().enumerate() {
17657                if i > 0 {
17658                    self.write(", ");
17659                }
17660                self.generate_expression(arg)?;
17661            }
17662            self.write("], _u -> NOT _u IS NULL)");
17663            return Ok(());
17664        }
17665
17666        // Snowflake fixtures expect TO_VARIANT applied to arrays to keep ARRAY_CONSTRUCT(...)
17667        // rather than bracket-array syntax.
17668        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17669            && func.name.eq_ignore_ascii_case("TO_VARIANT")
17670            && func.args.len() == 1
17671        {
17672            let array_expressions = match &func.args[0] {
17673                Expression::ArrayFunc(arr) => Some(&arr.expressions),
17674                Expression::Array(arr) => Some(&arr.expressions),
17675                _ => None,
17676            };
17677            if let Some(expressions) = array_expressions {
17678                self.write_keyword("TO_VARIANT");
17679                self.write("(");
17680                self.write_keyword("ARRAY_CONSTRUCT");
17681                self.write("(");
17682                for (i, arg) in expressions.iter().enumerate() {
17683                    if i > 0 {
17684                        self.write(", ");
17685                    }
17686                    self.generate_expression(arg)?;
17687                }
17688                self.write(")");
17689                self.write(")");
17690                return Ok(());
17691            }
17692        }
17693
17694        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
17695        if func.name.eq_ignore_ascii_case("STRUCT")
17696            && !matches!(
17697                self.config.dialect,
17698                Some(DialectType::BigQuery)
17699                    | Some(DialectType::Spark)
17700                    | Some(DialectType::Databricks)
17701                    | Some(DialectType::Hive)
17702                    | None
17703            )
17704        {
17705            return self.generate_struct_function_cross_dialect(func);
17706        }
17707
17708        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
17709        // This is an internal marker function for ::? JSON path syntax
17710        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
17711            self.generate_expression(&func.args[0])?;
17712            self.write("::?");
17713            // Extract the key from the string literal
17714            if let Expression::Literal(lit) = &func.args[1] {
17715                if let crate::expressions::Literal::String(key) = lit.as_ref() {
17716                    self.write(key);
17717                }
17718            } else {
17719                self.generate_expression(&func.args[1])?;
17720            }
17721            return Ok(());
17722        }
17723
17724        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
17725        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
17726            self.generate_expression(&func.args[0])?;
17727            self.write(" # ");
17728            self.generate_expression(&func.args[1])?;
17729            return Ok(());
17730        }
17731
17732        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
17733        if matches!(
17734            self.config.dialect,
17735            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
17736        ) && func.name.eq_ignore_ascii_case("TRY")
17737            && func.args.len() == 1
17738        {
17739            self.generate_expression(&func.args[0])?;
17740            return Ok(());
17741        }
17742
17743        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
17744        if self.config.dialect == Some(DialectType::ClickHouse)
17745            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
17746            && func.args.len() == 1
17747        {
17748            self.write("dateTrunc('DAY', ");
17749            self.generate_expression(&func.args[0])?;
17750            self.write(")");
17751            return Ok(());
17752        }
17753
17754        // ClickHouse uses dateTrunc casing.
17755        if self.config.dialect == Some(DialectType::ClickHouse)
17756            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17757            && func.args.len() == 2
17758        {
17759            self.write("dateTrunc(");
17760            self.generate_expression(&func.args[0])?;
17761            self.write(", ");
17762            self.generate_expression(&func.args[1])?;
17763            self.write(")");
17764            return Ok(());
17765        }
17766
17767        // Presto-family dialects spell SUBSTRING as SUBSTR in SQLGlot outputs.
17768        if matches!(
17769            self.config.dialect,
17770            Some(DialectType::Presto | DialectType::Trino | DialectType::Athena)
17771        ) && func.name.eq_ignore_ascii_case("SUBSTRING")
17772        {
17773            self.write_keyword("SUBSTR");
17774            self.write("(");
17775            for (i, arg) in func.args.iter().enumerate() {
17776                if i > 0 {
17777                    self.write(", ");
17778                }
17779                self.generate_expression(arg)?;
17780            }
17781            self.write(")");
17782            return Ok(());
17783        }
17784
17785        if self.config.dialect == Some(DialectType::Snowflake)
17786            && func.name.eq_ignore_ascii_case("LIST_DISTINCT")
17787            && func.args.len() == 1
17788        {
17789            self.write_keyword("ARRAY_DISTINCT");
17790            self.write("(");
17791            self.write_keyword("ARRAY_COMPACT");
17792            self.write("(");
17793            self.generate_expression(&func.args[0])?;
17794            self.write("))");
17795            return Ok(());
17796        }
17797
17798        if self.config.dialect == Some(DialectType::Snowflake)
17799            && func.name.eq_ignore_ascii_case("LIST")
17800            && func.args.len() == 1
17801            && !matches!(func.args.first(), Some(Expression::Select(_)))
17802        {
17803            self.write_keyword("ARRAY_AGG");
17804            self.write("(");
17805            self.generate_expression(&func.args[0])?;
17806            self.write(")");
17807            return Ok(());
17808        }
17809
17810        // Redshift: CONCAT(a, b, ...) -> a || b || ...
17811        if self.config.dialect == Some(DialectType::Redshift)
17812            && func.name.eq_ignore_ascii_case("CONCAT")
17813            && func.args.len() >= 2
17814        {
17815            for (i, arg) in func.args.iter().enumerate() {
17816                if i > 0 {
17817                    self.write(" || ");
17818                }
17819                self.generate_expression(arg)?;
17820            }
17821            return Ok(());
17822        }
17823
17824        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
17825        if self.config.dialect == Some(DialectType::Redshift)
17826            && func.name.eq_ignore_ascii_case("CONCAT_WS")
17827            && func.args.len() >= 2
17828        {
17829            let sep = &func.args[0];
17830            for (i, arg) in func.args.iter().skip(1).enumerate() {
17831                if i > 0 {
17832                    self.write(" || ");
17833                    self.generate_expression(sep)?;
17834                    self.write(" || ");
17835                }
17836                self.generate_expression(arg)?;
17837            }
17838            return Ok(());
17839        }
17840
17841        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
17842        // Unit should be unquoted uppercase identifier
17843        if self.config.dialect == Some(DialectType::Redshift)
17844            && (func.name.eq_ignore_ascii_case("DATEDIFF")
17845                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
17846            && func.args.len() == 3
17847        {
17848            self.write_keyword("DATEDIFF");
17849            self.write("(");
17850            // First arg is unit - normalize to unquoted uppercase
17851            self.write_redshift_date_part(&func.args[0]);
17852            self.write(", ");
17853            self.generate_expression(&func.args[1])?;
17854            self.write(", ");
17855            self.generate_expression(&func.args[2])?;
17856            self.write(")");
17857            return Ok(());
17858        }
17859
17860        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
17861        // Unit should be unquoted uppercase identifier
17862        if self.config.dialect == Some(DialectType::Redshift)
17863            && (func.name.eq_ignore_ascii_case("DATEADD")
17864                || func.name.eq_ignore_ascii_case("DATE_ADD"))
17865            && func.args.len() == 3
17866        {
17867            self.write_keyword("DATEADD");
17868            self.write("(");
17869            // First arg is unit - normalize to unquoted uppercase
17870            self.write_redshift_date_part(&func.args[0]);
17871            self.write(", ");
17872            self.generate_expression(&func.args[1])?;
17873            self.write(", ");
17874            self.generate_expression(&func.args[2])?;
17875            self.write(")");
17876            return Ok(());
17877        }
17878
17879        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function.
17880        if func.name.eq_ignore_ascii_case("UUID_STRING")
17881            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
17882        {
17883            if matches!(
17884                self.config.dialect,
17885                Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
17886            ) {
17887                self.write_keyword("CAST");
17888                self.write("(");
17889                self.write_keyword("UUID");
17890                self.write("() ");
17891                self.write_keyword("AS");
17892                self.write(" ");
17893                self.write_keyword("STRING");
17894                self.write(")");
17895                return Ok(());
17896            }
17897
17898            if matches!(
17899                self.config.dialect,
17900                Some(DialectType::Presto | DialectType::Trino)
17901            ) {
17902                self.write_keyword("CAST");
17903                self.write("(");
17904                self.write_keyword("UUID");
17905                self.write("() ");
17906                self.write_keyword("AS");
17907                self.write(" ");
17908                self.write_keyword("VARCHAR");
17909                self.write(")");
17910                return Ok(());
17911            }
17912
17913            if self.config.dialect == Some(DialectType::DuckDB) && func.args.len() == 2 {
17914                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(");
17915                self.generate_expression(&func.args[0])?;
17916                self.write(", '-', '')) || ENCODE(");
17917                self.generate_expression(&func.args[1])?;
17918                self.write(")), 1, 32) AS h))");
17919                return Ok(());
17920            }
17921
17922            let func_name = match self.config.dialect {
17923                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
17924                Some(DialectType::BigQuery) => "GENERATE_UUID",
17925                _ => "UUID",
17926            };
17927            self.write_keyword(func_name);
17928            self.write("()");
17929            return Ok(());
17930        }
17931
17932        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
17933        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
17934        // Positional args are mapped to named parameters.
17935        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17936            && func.name.eq_ignore_ascii_case("GENERATOR")
17937        {
17938            let has_positional_args =
17939                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
17940            if has_positional_args {
17941                let param_names = ["ROWCOUNT", "TIMELIMIT"];
17942                self.write_keyword("GENERATOR");
17943                self.write("(");
17944                for (i, arg) in func.args.iter().enumerate() {
17945                    if i > 0 {
17946                        self.write(", ");
17947                    }
17948                    if i < param_names.len() {
17949                        self.write_keyword(param_names[i]);
17950                        self.write(" => ");
17951                        self.generate_expression(arg)?;
17952                    } else {
17953                        self.generate_expression(arg)?;
17954                    }
17955                }
17956                self.write(")");
17957                return Ok(());
17958            }
17959        }
17960
17961        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
17962        // Unit should be quoted uppercase string
17963        if self.config.dialect == Some(DialectType::Redshift)
17964            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17965            && func.args.len() == 2
17966        {
17967            self.write_keyword("DATE_TRUNC");
17968            self.write("(");
17969            // First arg is unit - normalize to quoted uppercase
17970            self.write_redshift_date_part_quoted(&func.args[0]);
17971            self.write(", ");
17972            self.generate_expression(&func.args[1])?;
17973            self.write(")");
17974            return Ok(());
17975        }
17976
17977        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
17978        if matches!(
17979            self.config.dialect,
17980            Some(DialectType::TSQL) | Some(DialectType::Fabric)
17981        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17982            || func.name.eq_ignore_ascii_case("DATEPART"))
17983            && func.args.len() == 2
17984        {
17985            self.write_keyword("DATEPART");
17986            self.write("(");
17987            self.write_tsql_date_part(&func.args[0])?;
17988            self.write(", ");
17989            self.generate_expression(&func.args[1])?;
17990            self.write(")");
17991            return Ok(());
17992        }
17993
17994        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
17995        if matches!(
17996            self.config.dialect,
17997            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
17998        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17999            || func.name.eq_ignore_ascii_case("DATEPART"))
18000            && func.args.len() == 2
18001        {
18002            self.write_keyword("EXTRACT");
18003            self.write("(");
18004            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
18005            match &func.args[0] {
18006                Expression::Literal(lit)
18007                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18008                {
18009                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
18010                        unreachable!()
18011                    };
18012                    self.write(&s.to_ascii_lowercase());
18013                }
18014                _ => self.generate_expression(&func.args[0])?,
18015            }
18016            self.write_space();
18017            self.write_keyword("FROM");
18018            self.write_space();
18019            self.generate_expression(&func.args[1])?;
18020            self.write(")");
18021            return Ok(());
18022        }
18023
18024        // PostgreSQL: DATE_ADD(date, INTERVAL '...') / DATE_SUB(...) -> infix interval arithmetic.
18025        if self.config.dialect == Some(DialectType::PostgreSQL)
18026            && matches!(
18027                func.name.to_ascii_uppercase().as_str(),
18028                "DATE_ADD" | "DATE_SUB"
18029            )
18030            && func.args.len() == 2
18031            && matches!(func.args[1], Expression::Interval(_))
18032        {
18033            self.generate_expression(&func.args[0])?;
18034            self.write_space();
18035            if func.name.eq_ignore_ascii_case("DATE_SUB") {
18036                self.write("-");
18037            } else {
18038                self.write("+");
18039            }
18040            self.write_space();
18041            self.generate_expression(&func.args[1])?;
18042            return Ok(());
18043        }
18044
18045        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
18046        // Also DATE literals in Dremio should be CAST(...AS DATE)
18047        if self.config.dialect == Some(DialectType::Dremio)
18048            && (func.name.eq_ignore_ascii_case("DATE_PART")
18049                || func.name.eq_ignore_ascii_case("DATEPART"))
18050            && func.args.len() == 2
18051        {
18052            self.write_keyword("EXTRACT");
18053            self.write("(");
18054            self.generate_expression(&func.args[0])?;
18055            self.write_space();
18056            self.write_keyword("FROM");
18057            self.write_space();
18058            // For Dremio, DATE literals should become CAST('value' AS DATE)
18059            self.generate_dremio_date_expression(&func.args[1])?;
18060            self.write(")");
18061            return Ok(());
18062        }
18063
18064        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
18065        if self.config.dialect == Some(DialectType::Dremio)
18066            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
18067            && func.args.is_empty()
18068        {
18069            self.write_keyword("CURRENT_DATE_UTC");
18070            return Ok(());
18071        }
18072
18073        // Dremio: DATETYPE(year, month, day) transformation
18074        // - If all args are integer literals: DATE('YYYY-MM-DD')
18075        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
18076        if self.config.dialect == Some(DialectType::Dremio)
18077            && func.name.eq_ignore_ascii_case("DATETYPE")
18078            && func.args.len() == 3
18079        {
18080            // Helper function to extract integer from number literal
18081            fn get_int_literal(expr: &Expression) -> Option<i64> {
18082                if let Expression::Literal(lit) = expr {
18083                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
18084                        s.parse::<i64>().ok()
18085                    } else {
18086                        None
18087                    }
18088                } else {
18089                    None
18090                }
18091            }
18092
18093            // Check if all arguments are integer literals
18094            if let (Some(year), Some(month), Some(day)) = (
18095                get_int_literal(&func.args[0]),
18096                get_int_literal(&func.args[1]),
18097                get_int_literal(&func.args[2]),
18098            ) {
18099                // All are integer literals: DATE('YYYY-MM-DD')
18100                self.write_keyword("DATE");
18101                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
18102                return Ok(());
18103            }
18104
18105            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
18106            self.write_keyword("CAST");
18107            self.write("(");
18108            self.write_keyword("CONCAT");
18109            self.write("(");
18110            self.generate_expression(&func.args[0])?;
18111            self.write(", '-', ");
18112            self.generate_expression(&func.args[1])?;
18113            self.write(", '-', ");
18114            self.generate_expression(&func.args[2])?;
18115            self.write(")");
18116            self.write_space();
18117            self.write_keyword("AS");
18118            self.write_space();
18119            self.write_keyword("DATE");
18120            self.write(")");
18121            return Ok(());
18122        }
18123
18124        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
18125        // when it's not an integer literal
18126        let is_presto_like = matches!(
18127            self.config.dialect,
18128            Some(DialectType::Presto) | Some(DialectType::Trino)
18129        );
18130        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
18131            self.write_keyword("DATE_ADD");
18132            self.write("(");
18133            // First arg: unit (pass through as-is, e.g., 'DAY')
18134            self.generate_expression(&func.args[0])?;
18135            self.write(", ");
18136            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
18137            let interval = &func.args[1];
18138            let needs_cast = !self.returns_integer_type(interval);
18139            if needs_cast {
18140                self.write_keyword("CAST");
18141                self.write("(");
18142            }
18143            self.generate_expression(interval)?;
18144            if needs_cast {
18145                self.write_space();
18146                self.write_keyword("AS");
18147                self.write_space();
18148                self.write_keyword("BIGINT");
18149                self.write(")");
18150            }
18151            self.write(", ");
18152            // Third arg: date
18153            self.generate_expression(&func.args[2])?;
18154            self.write(")");
18155            return Ok(());
18156        }
18157
18158        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
18159        let use_brackets = func.use_bracket_syntax;
18160
18161        // Special case: functions WITH ORDINALITY need special output order
18162        // Input: FUNC(args) WITH ORDINALITY
18163        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
18164        // Output must be: FUNC(args) WITH ORDINALITY
18165        let has_ordinality = func.name.len() >= 16
18166            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
18167        let output_name = if has_ordinality {
18168            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
18169            self.normalize_func_name(base_name)
18170        } else {
18171            normalized_name.clone()
18172        };
18173
18174        // For qualified names (schema.function or object.method), preserve original case
18175        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
18176        let quote_source_clickhouse_function =
18177            matches!(self.config.dialect, Some(DialectType::ClickHouse))
18178                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
18179                && func.quoted;
18180
18181        if quote_source_clickhouse_function {
18182            self.generate_identifier(&Identifier {
18183                name: func.name.clone(),
18184                quoted: true,
18185                trailing_comments: Vec::new(),
18186                span: None,
18187            })?;
18188        } else if func.name.contains('.') && !has_ordinality {
18189            // Don't normalize qualified functions - preserve original case
18190            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
18191            if func.quoted {
18192                self.write("`");
18193                self.write(&func.name);
18194                self.write("`");
18195            } else {
18196                self.write(&func.name);
18197            }
18198        } else {
18199            self.write(&output_name);
18200        }
18201
18202        // If no_parens is true and there are no args, output just the function name
18203        // Unless the target dialect requires parens for this function
18204        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
18205            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
18206                || func.name.eq_ignore_ascii_case("SESSION_USER")
18207                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
18208            {
18209                matches!(
18210                    self.config.dialect,
18211                    Some(DialectType::Snowflake)
18212                        | Some(DialectType::Spark)
18213                        | Some(DialectType::Databricks)
18214                        | Some(DialectType::Hive)
18215                )
18216            } else {
18217                false
18218            };
18219            !needs_parens
18220        };
18221        if force_parens {
18222            // Output trailing comments
18223            for comment in &func.trailing_comments {
18224                self.write_space();
18225                self.write_formatted_comment(comment);
18226            }
18227            return Ok(());
18228        }
18229
18230        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
18231        if func.name.eq_ignore_ascii_case("CUBE")
18232            || func.name.eq_ignore_ascii_case("ROLLUP")
18233            || func.name.eq_ignore_ascii_case("GROUPING SETS")
18234        {
18235            self.write(" (");
18236        } else if use_brackets {
18237            self.write("[");
18238        } else {
18239            self.write("(");
18240        }
18241        if func.distinct {
18242            self.write_keyword("DISTINCT");
18243            self.write_space();
18244        }
18245
18246        // Check if arguments should be split onto multiple lines (pretty + too wide)
18247        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
18248            && (func.name.eq_ignore_ascii_case("TABLE")
18249                || func.name.eq_ignore_ascii_case("FLATTEN"));
18250        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
18251        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
18252            || func.name.eq_ignore_ascii_case("CUBE")
18253            || func.name.eq_ignore_ascii_case("ROLLUP");
18254        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
18255            if is_grouping_func {
18256                true
18257            } else {
18258                // Pre-render arguments to check total width
18259                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
18260                for arg in &func.args {
18261                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
18262                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
18263                    temp_gen.generate_expression(arg)?;
18264                    expr_strings.push(temp_gen.output);
18265                }
18266                self.too_wide(&expr_strings)
18267            }
18268        } else {
18269            false
18270        };
18271
18272        if should_split {
18273            // Split onto multiple lines
18274            self.write_newline();
18275            self.indent_level += 1;
18276            for (i, arg) in func.args.iter().enumerate() {
18277                self.write_indent();
18278                self.generate_expression(arg)?;
18279                if i + 1 < func.args.len() {
18280                    self.write(",");
18281                }
18282                self.write_newline();
18283            }
18284            self.indent_level -= 1;
18285            self.write_indent();
18286        } else {
18287            // All on one line
18288            for (i, arg) in func.args.iter().enumerate() {
18289                if i > 0 {
18290                    self.write(", ");
18291                }
18292                self.generate_expression(arg)?;
18293            }
18294        }
18295
18296        if use_brackets {
18297            self.write("]");
18298        } else {
18299            self.write(")");
18300        }
18301        // Append WITH ORDINALITY after closing paren for table-valued functions
18302        if has_ordinality {
18303            self.write_space();
18304            self.write_keyword("WITH ORDINALITY");
18305        }
18306        // Output trailing comments
18307        for comment in &func.trailing_comments {
18308            self.write_space();
18309            self.write_formatted_comment(comment);
18310        }
18311        Ok(())
18312    }
18313
18314    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
18315        self.generate_expression(&fe.this)?;
18316        self.write_keyword(" EMITS ");
18317        self.generate_expression(&fe.emits)?;
18318        Ok(())
18319    }
18320
18321    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
18322        // Normalize function name based on dialect settings
18323        let mut normalized_name = self.normalize_func_name(&func.name);
18324
18325        // Dialect-specific name mappings for aggregate functions
18326        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
18327            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
18328            match self.config.dialect {
18329                Some(DialectType::ClickHouse) => {
18330                    normalized_name = if is_max {
18331                        Cow::Borrowed("argMax")
18332                    } else {
18333                        Cow::Borrowed("argMin")
18334                    };
18335                }
18336                Some(DialectType::DuckDB) => {
18337                    normalized_name = if is_max {
18338                        Cow::Borrowed("ARG_MAX")
18339                    } else {
18340                        Cow::Borrowed("ARG_MIN")
18341                    };
18342                }
18343                _ => {}
18344            }
18345        }
18346        self.write(normalized_name.as_ref());
18347        self.write("(");
18348        if func.distinct {
18349            self.write_keyword("DISTINCT");
18350            self.write_space();
18351        }
18352
18353        // Check if we need to transform multi-arg COUNT DISTINCT
18354        // When dialect doesn't support multi_arg_distinct, transform:
18355        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
18356        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
18357        let needs_multi_arg_transform =
18358            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
18359
18360        if needs_multi_arg_transform {
18361            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
18362            self.write_keyword("CASE");
18363            for arg in &func.args {
18364                self.write_space();
18365                self.write_keyword("WHEN");
18366                self.write_space();
18367                self.generate_expression(arg)?;
18368                self.write_space();
18369                self.write_keyword("IS NULL THEN NULL");
18370            }
18371            self.write_space();
18372            self.write_keyword("ELSE");
18373            self.write(" (");
18374            for (i, arg) in func.args.iter().enumerate() {
18375                if i > 0 {
18376                    self.write(", ");
18377                }
18378                self.generate_expression(arg)?;
18379            }
18380            self.write(")");
18381            self.write_space();
18382            self.write_keyword("END");
18383        } else {
18384            for (i, arg) in func.args.iter().enumerate() {
18385                if i > 0 {
18386                    self.write(", ");
18387                }
18388                self.generate_expression(arg)?;
18389            }
18390        }
18391
18392        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
18393        let clickhouse_ignore_nulls_outside =
18394            matches!(self.config.dialect, Some(DialectType::ClickHouse));
18395        if self.config.ignore_nulls_in_func
18396            && !matches!(
18397                self.config.dialect,
18398                Some(DialectType::DuckDB) | Some(DialectType::ClickHouse)
18399            )
18400        {
18401            if let Some(ignore) = func.ignore_nulls {
18402                self.write_space();
18403                if ignore {
18404                    self.write_keyword("IGNORE NULLS");
18405                } else {
18406                    self.write_keyword("RESPECT NULLS");
18407                }
18408            }
18409        }
18410
18411        // ORDER BY inside aggregate
18412        if !func.order_by.is_empty() {
18413            self.write_space();
18414            self.write_keyword("ORDER BY");
18415            self.write_space();
18416            for (i, ord) in func.order_by.iter().enumerate() {
18417                if i > 0 {
18418                    self.write(", ");
18419                }
18420                self.generate_ordered(ord)?;
18421            }
18422        }
18423
18424        // LIMIT inside aggregate
18425        if let Some(limit) = &func.limit {
18426            self.write_space();
18427            self.write_keyword("LIMIT");
18428            self.write_space();
18429            // Check if this is a Tuple representing LIMIT offset, count
18430            if let Expression::Tuple(t) = limit.as_ref() {
18431                if t.expressions.len() == 2 {
18432                    self.generate_expression(&t.expressions[0])?;
18433                    self.write(", ");
18434                    self.generate_expression(&t.expressions[1])?;
18435                } else {
18436                    self.generate_expression(limit)?;
18437                }
18438            } else {
18439                self.generate_expression(limit)?;
18440            }
18441        }
18442
18443        self.write(")");
18444
18445        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
18446        if (!self.config.ignore_nulls_in_func || clickhouse_ignore_nulls_outside)
18447            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18448        {
18449            if let Some(ignore) = func.ignore_nulls {
18450                self.write_space();
18451                if ignore {
18452                    self.write_keyword("IGNORE NULLS");
18453                } else {
18454                    self.write_keyword("RESPECT NULLS");
18455                }
18456            }
18457        }
18458
18459        if let Some(filter) = &func.filter {
18460            self.write_space();
18461            self.write_keyword("FILTER");
18462            self.write("(");
18463            self.write_keyword("WHERE");
18464            self.write_space();
18465            self.generate_expression(filter)?;
18466            self.write(")");
18467        }
18468
18469        Ok(())
18470    }
18471
18472    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
18473        self.generate_expression(&wf.this)?;
18474
18475        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
18476        if let Some(keep) = &wf.keep {
18477            self.write_space();
18478            self.write_keyword("KEEP");
18479            self.write(" (");
18480            self.write_keyword("DENSE_RANK");
18481            self.write_space();
18482            if keep.first {
18483                self.write_keyword("FIRST");
18484            } else {
18485                self.write_keyword("LAST");
18486            }
18487            self.write_space();
18488            self.write_keyword("ORDER BY");
18489            self.write_space();
18490            for (i, ord) in keep.order_by.iter().enumerate() {
18491                if i > 0 {
18492                    self.write(", ");
18493                }
18494                self.generate_ordered(ord)?;
18495            }
18496            self.write(")");
18497        }
18498
18499        // Check if there's any OVER clause content
18500        let has_over = !wf.over.partition_by.is_empty()
18501            || !wf.over.order_by.is_empty()
18502            || wf.over.frame.is_some()
18503            || wf.over.window_name.is_some();
18504
18505        // Only output OVER if there's actual window specification (not just KEEP alone)
18506        if has_over {
18507            self.write_space();
18508            self.write_keyword("OVER");
18509
18510            // Check if this is just a bare named window reference (no parens needed)
18511            let has_specs = !wf.over.partition_by.is_empty()
18512                || !wf.over.order_by.is_empty()
18513                || wf.over.frame.is_some();
18514
18515            if wf.over.window_name.is_some() && !has_specs {
18516                // OVER window_name (without parentheses)
18517                self.write_space();
18518                self.write(&wf.over.window_name.as_ref().unwrap().name);
18519            } else {
18520                // OVER (...) or OVER (window_name ...)
18521                self.write(" (");
18522                self.generate_over(&wf.over)?;
18523                self.write(")");
18524            }
18525        } else if wf.keep.is_none() {
18526            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
18527            self.write_space();
18528            self.write_keyword("OVER");
18529            self.write(" ()");
18530        }
18531
18532        Ok(())
18533    }
18534
18535    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
18536    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
18537        self.generate_expression(&wg.this)?;
18538        self.write_space();
18539        self.write_keyword("WITHIN GROUP");
18540        self.write(" (");
18541        self.write_keyword("ORDER BY");
18542        self.write_space();
18543        for (i, ord) in wg.order_by.iter().enumerate() {
18544            if i > 0 {
18545                self.write(", ");
18546            }
18547            self.generate_ordered(ord)?;
18548        }
18549        self.write(")");
18550        Ok(())
18551    }
18552
18553    /// Generate the contents of an OVER clause (without parentheses)
18554    fn generate_over(&mut self, over: &Over) -> Result<()> {
18555        let mut has_content = false;
18556
18557        // Named window reference
18558        if let Some(name) = &over.window_name {
18559            self.write(&name.name);
18560            has_content = true;
18561        }
18562
18563        // PARTITION BY
18564        if !over.partition_by.is_empty() {
18565            if has_content {
18566                self.write_space();
18567            }
18568            self.write_keyword("PARTITION BY");
18569            self.write_space();
18570            for (i, expr) in over.partition_by.iter().enumerate() {
18571                if i > 0 {
18572                    self.write(", ");
18573                }
18574                self.generate_expression(expr)?;
18575            }
18576            has_content = true;
18577        }
18578
18579        // ORDER BY
18580        if !over.order_by.is_empty() {
18581            if has_content {
18582                self.write_space();
18583            }
18584            self.write_keyword("ORDER BY");
18585            self.write_space();
18586            for (i, ordered) in over.order_by.iter().enumerate() {
18587                if i > 0 {
18588                    self.write(", ");
18589                }
18590                self.generate_ordered(ordered)?;
18591            }
18592            has_content = true;
18593        }
18594
18595        // Window frame
18596        if let Some(frame) = &over.frame {
18597            if has_content {
18598                self.write_space();
18599            }
18600            self.generate_window_frame(frame)?;
18601        }
18602
18603        Ok(())
18604    }
18605
18606    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
18607        // Exasol uses lowercase for frame kind (rows/range/groups)
18608        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18609
18610        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
18611        if !lowercase_frame {
18612            if let Some(kind_text) = &frame.kind_text {
18613                self.write(kind_text);
18614            } else {
18615                match frame.kind {
18616                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
18617                    WindowFrameKind::Range => self.write_keyword("RANGE"),
18618                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
18619                }
18620            }
18621        } else {
18622            match frame.kind {
18623                WindowFrameKind::Rows => self.write("rows"),
18624                WindowFrameKind::Range => self.write("range"),
18625                WindowFrameKind::Groups => self.write("groups"),
18626            }
18627        }
18628
18629        // Use BETWEEN format only when there's an explicit end bound,
18630        // or when normalize_window_frame_between is enabled and the start is a directional bound
18631        self.write_space();
18632        let should_normalize = self.config.normalize_window_frame_between
18633            && frame.end.is_none()
18634            && matches!(
18635                frame.start,
18636                WindowFrameBound::Preceding(_)
18637                    | WindowFrameBound::Following(_)
18638                    | WindowFrameBound::UnboundedPreceding
18639                    | WindowFrameBound::UnboundedFollowing
18640            );
18641
18642        if let Some(end) = &frame.end {
18643            // BETWEEN format: RANGE BETWEEN start AND end
18644            self.write_keyword("BETWEEN");
18645            self.write_space();
18646            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18647            self.write_space();
18648            self.write_keyword("AND");
18649            self.write_space();
18650            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
18651        } else if should_normalize {
18652            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
18653            self.write_keyword("BETWEEN");
18654            self.write_space();
18655            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18656            self.write_space();
18657            self.write_keyword("AND");
18658            self.write_space();
18659            self.write_keyword("CURRENT ROW");
18660        } else {
18661            // Single bound format: RANGE CURRENT ROW
18662            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18663        }
18664
18665        // EXCLUDE clause
18666        if let Some(exclude) = &frame.exclude {
18667            self.write_space();
18668            self.write_keyword("EXCLUDE");
18669            self.write_space();
18670            match exclude {
18671                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
18672                WindowFrameExclude::Group => self.write_keyword("GROUP"),
18673                WindowFrameExclude::Ties => self.write_keyword("TIES"),
18674                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
18675            }
18676        }
18677
18678        Ok(())
18679    }
18680
18681    fn generate_window_frame_bound(
18682        &mut self,
18683        bound: &WindowFrameBound,
18684        side_text: Option<&str>,
18685    ) -> Result<()> {
18686        // Exasol uses lowercase for preceding/following
18687        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18688
18689        match bound {
18690            WindowFrameBound::CurrentRow => {
18691                self.write_keyword("CURRENT ROW");
18692            }
18693            WindowFrameBound::UnboundedPreceding => {
18694                self.write_keyword("UNBOUNDED");
18695                self.write_space();
18696                if lowercase_frame {
18697                    self.write("preceding");
18698                } else if let Some(text) = side_text {
18699                    self.write(text);
18700                } else {
18701                    self.write_keyword("PRECEDING");
18702                }
18703            }
18704            WindowFrameBound::UnboundedFollowing => {
18705                self.write_keyword("UNBOUNDED");
18706                self.write_space();
18707                if lowercase_frame {
18708                    self.write("following");
18709                } else if let Some(text) = side_text {
18710                    self.write(text);
18711                } else {
18712                    self.write_keyword("FOLLOWING");
18713                }
18714            }
18715            WindowFrameBound::Preceding(expr) => {
18716                self.generate_expression(expr)?;
18717                self.write_space();
18718                if lowercase_frame {
18719                    self.write("preceding");
18720                } else if let Some(text) = side_text {
18721                    self.write(text);
18722                } else {
18723                    self.write_keyword("PRECEDING");
18724                }
18725            }
18726            WindowFrameBound::Following(expr) => {
18727                self.generate_expression(expr)?;
18728                self.write_space();
18729                if lowercase_frame {
18730                    self.write("following");
18731                } else if let Some(text) = side_text {
18732                    self.write(text);
18733                } else {
18734                    self.write_keyword("FOLLOWING");
18735                }
18736            }
18737            WindowFrameBound::BarePreceding => {
18738                if lowercase_frame {
18739                    self.write("preceding");
18740                } else if let Some(text) = side_text {
18741                    self.write(text);
18742                } else {
18743                    self.write_keyword("PRECEDING");
18744                }
18745            }
18746            WindowFrameBound::BareFollowing => {
18747                if lowercase_frame {
18748                    self.write("following");
18749                } else if let Some(text) = side_text {
18750                    self.write(text);
18751                } else {
18752                    self.write_keyword("FOLLOWING");
18753                }
18754            }
18755            WindowFrameBound::Value(expr) => {
18756                // Bare numeric bound without PRECEDING/FOLLOWING
18757                self.generate_expression(expr)?;
18758            }
18759        }
18760        Ok(())
18761    }
18762
18763    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
18764        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
18765        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
18766        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
18767            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
18768            && !matches!(&interval.this, Some(Expression::Literal(_)));
18769
18770        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
18771        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
18772        if self.config.single_string_interval {
18773            if let (
18774                Some(Expression::Literal(lit)),
18775                Some(IntervalUnitSpec::Simple {
18776                    ref unit,
18777                    ref use_plural,
18778                }),
18779            ) = (&interval.this, &interval.unit)
18780            {
18781                if let Literal::String(ref val) = lit.as_ref() {
18782                    self.write_keyword("INTERVAL");
18783                    self.write_space();
18784                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
18785                    let unit_str = self.interval_unit_str(unit, effective_plural);
18786                    self.write("'");
18787                    self.write(val);
18788                    self.write(" ");
18789                    self.write(&unit_str);
18790                    self.write("'");
18791                    return Ok(());
18792                }
18793            }
18794        }
18795
18796        if !skip_interval_keyword {
18797            self.write_keyword("INTERVAL");
18798        }
18799
18800        // Generate value if present
18801        if let Some(ref value) = interval.this {
18802            if !skip_interval_keyword {
18803                self.write_space();
18804            }
18805            // If the value is a complex expression (not a literal/column/function call)
18806            // and there's a unit, wrap it in parentheses
18807            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
18808            let needs_parens = interval.unit.is_some()
18809                && matches!(
18810                    value,
18811                    Expression::Add(_)
18812                        | Expression::Sub(_)
18813                        | Expression::Mul(_)
18814                        | Expression::Div(_)
18815                        | Expression::Mod(_)
18816                        | Expression::BitwiseAnd(_)
18817                        | Expression::BitwiseOr(_)
18818                        | Expression::BitwiseXor(_)
18819                );
18820            if needs_parens {
18821                self.write("(");
18822            }
18823            self.generate_expression(value)?;
18824            if needs_parens {
18825                self.write(")");
18826            }
18827        }
18828
18829        // Generate unit if present
18830        if let Some(ref unit_spec) = interval.unit {
18831            self.write_space();
18832            self.write_interval_unit_spec(unit_spec)?;
18833        }
18834
18835        Ok(())
18836    }
18837
18838    /// Return the string representation of an interval unit
18839    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
18840        match (unit, use_plural) {
18841            (IntervalUnit::Year, false) => "YEAR",
18842            (IntervalUnit::Year, true) => "YEARS",
18843            (IntervalUnit::Quarter, false) => "QUARTER",
18844            (IntervalUnit::Quarter, true) => "QUARTERS",
18845            (IntervalUnit::Month, false) => "MONTH",
18846            (IntervalUnit::Month, true) => "MONTHS",
18847            (IntervalUnit::Week, false) => "WEEK",
18848            (IntervalUnit::Week, true) => "WEEKS",
18849            (IntervalUnit::Day, false) => "DAY",
18850            (IntervalUnit::Day, true) => "DAYS",
18851            (IntervalUnit::Hour, false) => "HOUR",
18852            (IntervalUnit::Hour, true) => "HOURS",
18853            (IntervalUnit::Minute, false) => "MINUTE",
18854            (IntervalUnit::Minute, true) => "MINUTES",
18855            (IntervalUnit::Second, false) => "SECOND",
18856            (IntervalUnit::Second, true) => "SECONDS",
18857            (IntervalUnit::Millisecond, false) => "MILLISECOND",
18858            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
18859            (IntervalUnit::Microsecond, false) => "MICROSECOND",
18860            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
18861            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
18862            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
18863        }
18864    }
18865
18866    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
18867        match unit_spec {
18868            IntervalUnitSpec::Simple { unit, use_plural } => {
18869                // If dialect doesn't allow plural forms, force singular
18870                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
18871                self.write_simple_interval_unit(unit, effective_plural);
18872            }
18873            IntervalUnitSpec::Span(span) => {
18874                self.write_simple_interval_unit(&span.this, false);
18875                self.write_space();
18876                self.write_keyword("TO");
18877                self.write_space();
18878                self.write_simple_interval_unit(&span.expression, false);
18879            }
18880            IntervalUnitSpec::ExprSpan(span) => {
18881                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
18882                self.generate_expression(&span.this)?;
18883                self.write_space();
18884                self.write_keyword("TO");
18885                self.write_space();
18886                self.generate_expression(&span.expression)?;
18887            }
18888            IntervalUnitSpec::Expr(expr) => {
18889                self.generate_expression(expr)?;
18890            }
18891        }
18892        Ok(())
18893    }
18894
18895    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
18896        // Output interval unit, respecting plural preference
18897        match (unit, use_plural) {
18898            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
18899            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
18900            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
18901            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
18902            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
18903            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
18904            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
18905            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
18906            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
18907            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
18908            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
18909            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
18910            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
18911            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
18912            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
18913            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
18914            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
18915            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
18916            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
18917            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
18918            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
18919            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
18920        }
18921    }
18922
18923    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
18924    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
18925    fn write_redshift_date_part(&mut self, expr: &Expression) {
18926        let part_str = self.extract_date_part_string(expr);
18927        if let Some(part) = part_str {
18928            let normalized = self.normalize_date_part(&part);
18929            self.write_keyword(&normalized);
18930        } else {
18931            // If we can't extract a date part string, fall back to generating the expression
18932            let _ = self.generate_expression(expr);
18933        }
18934    }
18935
18936    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
18937    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
18938    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
18939        let part_str = self.extract_date_part_string(expr);
18940        if let Some(part) = part_str {
18941            let normalized = self.normalize_date_part(&part);
18942            self.write("'");
18943            self.write(&normalized);
18944            self.write("'");
18945        } else {
18946            // If we can't extract a date part string, fall back to generating the expression
18947            let _ = self.generate_expression(expr);
18948        }
18949    }
18950
18951    fn write_tsql_date_part(&mut self, expr: &Expression) -> Result<()> {
18952        if let Some(part) = self.extract_date_part_string(expr) {
18953            let upper = part.to_ascii_uppercase();
18954            let unmapped = match upper.as_str() {
18955                "DAYOFWEEK" => "WEEKDAY",
18956                "WEEKISO" => "ISO_WEEK",
18957                "TIMEZONE_MINUTE" => "TZOFFSET",
18958                _ => part.as_str(),
18959            };
18960            self.write_keyword(unmapped);
18961        } else {
18962            self.generate_expression(expr)?;
18963        }
18964        Ok(())
18965    }
18966
18967    /// Extract date part string from expression (handles string literals and identifiers)
18968    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
18969        match expr {
18970            Expression::Literal(lit)
18971                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18972            {
18973                let crate::expressions::Literal::String(s) = lit.as_ref() else {
18974                    unreachable!()
18975                };
18976                Some(s.clone())
18977            }
18978            Expression::Identifier(id) => Some(id.name.clone()),
18979            Expression::Var(v) => Some(v.this.clone()),
18980            Expression::Column(col) if col.table.is_none() => {
18981                // Simple column reference without table prefix, treat as identifier
18982                Some(col.name.name.clone())
18983            }
18984            _ => None,
18985        }
18986    }
18987
18988    /// Normalize date part to uppercase singular form
18989    /// days -> DAY, months -> MONTH, etc.
18990    fn normalize_date_part(&self, part: &str) -> String {
18991        let mut buf = [0u8; 64];
18992        let lower: &str = if part.len() <= 64 {
18993            for (i, b) in part.bytes().enumerate() {
18994                buf[i] = b.to_ascii_lowercase();
18995            }
18996            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
18997        } else {
18998            return part.to_ascii_uppercase();
18999        };
19000        match lower {
19001            "day" | "days" | "d" => "DAY".to_string(),
19002            "month" | "months" | "mon" | "mons" | "mm" => "MONTH".to_string(),
19003            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
19004            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
19005            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
19006            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
19007            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
19008            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
19009            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
19010            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
19011            _ => part.to_ascii_uppercase(),
19012        }
19013    }
19014
19015    fn write_datetime_field(&mut self, field: &DateTimeField) {
19016        match field {
19017            DateTimeField::Year => self.write_keyword("YEAR"),
19018            DateTimeField::Month => self.write_keyword("MONTH"),
19019            DateTimeField::Day => self.write_keyword("DAY"),
19020            DateTimeField::Hour => self.write_keyword("HOUR"),
19021            DateTimeField::Minute => self.write_keyword("MINUTE"),
19022            DateTimeField::Second => self.write_keyword("SECOND"),
19023            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
19024            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
19025            DateTimeField::DayOfWeek => {
19026                let name = match self.config.dialect {
19027                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
19028                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
19029                    _ => "DOW",
19030                };
19031                self.write_keyword(name);
19032            }
19033            DateTimeField::DayOfYear => {
19034                let name = match self.config.dialect {
19035                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
19036                    _ => "DOY",
19037                };
19038                self.write_keyword(name);
19039            }
19040            DateTimeField::Week => self.write_keyword("WEEK"),
19041            DateTimeField::WeekWithModifier(modifier) => {
19042                self.write_keyword("WEEK");
19043                self.write("(");
19044                self.write(modifier);
19045                self.write(")");
19046            }
19047            DateTimeField::Quarter => self.write_keyword("QUARTER"),
19048            DateTimeField::Epoch => self.write_keyword("EPOCH"),
19049            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
19050            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
19051            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
19052            DateTimeField::Date => self.write_keyword("DATE"),
19053            DateTimeField::Time => self.write_keyword("TIME"),
19054            DateTimeField::Custom(name) => self.write(name),
19055        }
19056    }
19057
19058    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
19059    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
19060        match field {
19061            DateTimeField::Year => self.write("year"),
19062            DateTimeField::Month => self.write("month"),
19063            DateTimeField::Day => self.write("day"),
19064            DateTimeField::Hour => self.write("hour"),
19065            DateTimeField::Minute => self.write("minute"),
19066            DateTimeField::Second => self.write("second"),
19067            DateTimeField::Millisecond => self.write("millisecond"),
19068            DateTimeField::Microsecond => self.write("microsecond"),
19069            DateTimeField::DayOfWeek => self.write("dow"),
19070            DateTimeField::DayOfYear => self.write("doy"),
19071            DateTimeField::Week => self.write("week"),
19072            DateTimeField::WeekWithModifier(modifier) => {
19073                self.write("week(");
19074                self.write(modifier);
19075                self.write(")");
19076            }
19077            DateTimeField::Quarter => self.write("quarter"),
19078            DateTimeField::Epoch => self.write("epoch"),
19079            DateTimeField::Timezone => self.write("timezone"),
19080            DateTimeField::TimezoneHour => self.write("timezone_hour"),
19081            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
19082            DateTimeField::Date => self.write("date"),
19083            DateTimeField::Time => self.write("time"),
19084            DateTimeField::Custom(name) => self.write(name),
19085        }
19086    }
19087
19088    // Helper function generators
19089
19090    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
19091        self.write_keyword(name);
19092        self.write("(");
19093        self.generate_expression(arg)?;
19094        self.write(")");
19095        Ok(())
19096    }
19097
19098    /// Generate a unary function, using the original name if available for round-trip preservation
19099    fn generate_unary_func(
19100        &mut self,
19101        default_name: &str,
19102        f: &crate::expressions::UnaryFunc,
19103    ) -> Result<()> {
19104        let name = f.original_name.as_deref().unwrap_or(default_name);
19105        self.write_keyword(name);
19106        self.write("(");
19107        self.generate_expression(&f.this)?;
19108        self.write(")");
19109        Ok(())
19110    }
19111
19112    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
19113    fn generate_sqrt_cbrt(
19114        &mut self,
19115        f: &crate::expressions::UnaryFunc,
19116        func_name: &str,
19117        _op: &str,
19118    ) -> Result<()> {
19119        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
19120        // Always use function syntax for consistency
19121        self.write_keyword(func_name);
19122        self.write("(");
19123        self.generate_expression(&f.this)?;
19124        self.write(")");
19125        Ok(())
19126    }
19127
19128    fn generate_binary_func(
19129        &mut self,
19130        name: &str,
19131        arg1: &Expression,
19132        arg2: &Expression,
19133    ) -> Result<()> {
19134        self.write_keyword(name);
19135        self.write("(");
19136        self.generate_expression(arg1)?;
19137        self.write(", ");
19138        self.generate_expression(arg2)?;
19139        self.write(")");
19140        Ok(())
19141    }
19142
19143    /// Generate CHAR/CHR function with optional USING charset
19144    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
19145    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
19146    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
19147        // Use stored name if available, otherwise default to CHAR
19148        let func_name = f.name.as_deref().unwrap_or("CHAR");
19149        self.write_keyword(func_name);
19150        self.write("(");
19151        for (i, arg) in f.args.iter().enumerate() {
19152            if i > 0 {
19153                self.write(", ");
19154            }
19155            self.generate_expression(arg)?;
19156        }
19157        if let Some(ref charset) = f.charset {
19158            self.write(" ");
19159            self.write_keyword("USING");
19160            self.write(" ");
19161            self.write(charset);
19162        }
19163        self.write(")");
19164        Ok(())
19165    }
19166
19167    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
19168        use crate::dialects::DialectType;
19169
19170        match self.config.dialect {
19171            Some(DialectType::Teradata) => {
19172                // Teradata uses ** operator for exponentiation
19173                self.generate_expression(&f.this)?;
19174                self.write(" ** ");
19175                self.generate_expression(&f.expression)?;
19176                Ok(())
19177            }
19178            _ => {
19179                // Other dialects use POWER function
19180                self.generate_binary_func("POWER", &f.this, &f.expression)
19181            }
19182        }
19183    }
19184
19185    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
19186        self.write_func_name(name);
19187        self.write("(");
19188        for (i, arg) in args.iter().enumerate() {
19189            if i > 0 {
19190                self.write(", ");
19191            }
19192            self.generate_expression(arg)?;
19193        }
19194        self.write(")");
19195        Ok(())
19196    }
19197
19198    // String function generators
19199
19200    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
19201        self.write_keyword("CONCAT_WS");
19202        self.write("(");
19203        self.generate_expression(&f.separator)?;
19204        for expr in &f.expressions {
19205            self.write(", ");
19206            self.generate_expression(expr)?;
19207        }
19208        self.write(")");
19209        Ok(())
19210    }
19211
19212    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
19213        if let Expression::Concat(op) = expr {
19214            Self::collect_concat_operands(&op.left, out);
19215            Self::collect_concat_operands(&op.right, out);
19216        } else {
19217            out.push(expr);
19218        }
19219    }
19220
19221    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
19222        let mut operands = Vec::new();
19223        Self::collect_concat_operands(&op.left, &mut operands);
19224        Self::collect_concat_operands(&op.right, &mut operands);
19225
19226        self.write_keyword("CONCAT");
19227        self.write("(");
19228        for (i, operand) in operands.iter().enumerate() {
19229            if i > 0 {
19230                self.write(", ");
19231            }
19232            self.generate_expression(operand)?;
19233        }
19234        self.write(")");
19235        Ok(())
19236    }
19237
19238    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
19239        if let Expression::DPipe(dpipe) = expr {
19240            Self::collect_dpipe_operands(&dpipe.this, out);
19241            Self::collect_dpipe_operands(&dpipe.expression, out);
19242        } else {
19243            out.push(expr);
19244        }
19245    }
19246
19247    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
19248        let mut operands = Vec::new();
19249        Self::collect_dpipe_operands(&e.this, &mut operands);
19250        Self::collect_dpipe_operands(&e.expression, &mut operands);
19251
19252        self.write_keyword("CONCAT");
19253        self.write("(");
19254        for (i, operand) in operands.iter().enumerate() {
19255            if i > 0 {
19256                self.write(", ");
19257            }
19258            self.generate_expression(operand)?;
19259        }
19260        self.write(")");
19261        Ok(())
19262    }
19263
19264    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
19265        // Oracle and Presto-family dialects use SUBSTR; most others use SUBSTRING
19266        let use_substr = matches!(
19267            self.config.dialect,
19268            Some(
19269                DialectType::Oracle
19270                    | DialectType::Presto
19271                    | DialectType::Trino
19272                    | DialectType::Athena
19273            )
19274        );
19275        if use_substr {
19276            self.write_keyword("SUBSTR");
19277        } else {
19278            self.write_keyword("SUBSTRING");
19279        }
19280        self.write("(");
19281        self.generate_expression(&f.this)?;
19282        // PostgreSQL always uses FROM/FOR syntax
19283        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
19284        // Spark/Hive/TSQL/Fabric use comma syntax, not FROM/FOR syntax
19285        let use_comma_syntax = matches!(
19286            self.config.dialect,
19287            Some(DialectType::Spark)
19288                | Some(DialectType::Hive)
19289                | Some(DialectType::Databricks)
19290                | Some(DialectType::TSQL)
19291                | Some(DialectType::Fabric)
19292        );
19293        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
19294            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
19295            self.write_space();
19296            self.write_keyword("FROM");
19297            self.write_space();
19298            self.generate_expression(&f.start)?;
19299            if let Some(length) = &f.length {
19300                self.write_space();
19301                self.write_keyword("FOR");
19302                self.write_space();
19303                self.generate_expression(length)?;
19304            }
19305        } else {
19306            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
19307            self.write(", ");
19308            self.generate_expression(&f.start)?;
19309            if let Some(length) = &f.length {
19310                self.write(", ");
19311                self.generate_expression(length)?;
19312            }
19313        }
19314        self.write(")");
19315        Ok(())
19316    }
19317
19318    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
19319        self.write_keyword("OVERLAY");
19320        self.write("(");
19321        self.generate_expression(&f.this)?;
19322        self.write_space();
19323        self.write_keyword("PLACING");
19324        self.write_space();
19325        self.generate_expression(&f.replacement)?;
19326        self.write_space();
19327        self.write_keyword("FROM");
19328        self.write_space();
19329        self.generate_expression(&f.from)?;
19330        if let Some(length) = &f.length {
19331            self.write_space();
19332            self.write_keyword("FOR");
19333            self.write_space();
19334            self.generate_expression(length)?;
19335        }
19336        self.write(")");
19337        Ok(())
19338    }
19339
19340    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
19341        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
19342        // when no characters are specified (PostgreSQL style)
19343        if f.position_explicit && f.characters.is_none() {
19344            match f.position {
19345                TrimPosition::Leading => {
19346                    self.write_keyword("LTRIM");
19347                    self.write("(");
19348                    self.generate_expression(&f.this)?;
19349                    self.write(")");
19350                    return Ok(());
19351                }
19352                TrimPosition::Trailing => {
19353                    self.write_keyword("RTRIM");
19354                    self.write("(");
19355                    self.generate_expression(&f.this)?;
19356                    self.write(")");
19357                    return Ok(());
19358                }
19359                TrimPosition::Both => {
19360                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
19361                    // Fall through to standard TRIM handling
19362                }
19363            }
19364        }
19365
19366        self.write_keyword("TRIM");
19367        self.write("(");
19368        // When BOTH is specified without trim characters, simplify to just TRIM(str)
19369        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
19370        let force_standard = f.characters.is_some()
19371            && !f.sql_standard_syntax
19372            && matches!(
19373                self.config.dialect,
19374                Some(DialectType::Hive)
19375                    | Some(DialectType::Spark)
19376                    | Some(DialectType::Databricks)
19377                    | Some(DialectType::ClickHouse)
19378            );
19379        let use_standard = (f.sql_standard_syntax || force_standard)
19380            && !(f.position_explicit
19381                && f.characters.is_none()
19382                && matches!(f.position, TrimPosition::Both));
19383        if use_standard {
19384            // SQL standard syntax: TRIM(BOTH chars FROM str)
19385            // Only output position if it was explicitly specified
19386            if f.position_explicit {
19387                match f.position {
19388                    TrimPosition::Both => self.write_keyword("BOTH"),
19389                    TrimPosition::Leading => self.write_keyword("LEADING"),
19390                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
19391                }
19392                self.write_space();
19393            }
19394            if let Some(chars) = &f.characters {
19395                self.generate_expression(chars)?;
19396                self.write_space();
19397            }
19398            self.write_keyword("FROM");
19399            self.write_space();
19400            self.generate_expression(&f.this)?;
19401        } else {
19402            // Simple function syntax: TRIM(str) or TRIM(str, chars)
19403            self.generate_expression(&f.this)?;
19404            if let Some(chars) = &f.characters {
19405                self.write(", ");
19406                self.generate_expression(chars)?;
19407            }
19408        }
19409        self.write(")");
19410        Ok(())
19411    }
19412
19413    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
19414        self.write_keyword("REPLACE");
19415        self.write("(");
19416        self.generate_expression(&f.this)?;
19417        self.write(", ");
19418        self.generate_expression(&f.old)?;
19419        self.write(", ");
19420        self.generate_expression(&f.new)?;
19421        self.write(")");
19422        Ok(())
19423    }
19424
19425    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
19426        self.write_keyword(name);
19427        self.write("(");
19428        self.generate_expression(&f.this)?;
19429        self.write(", ");
19430        self.generate_expression(&f.length)?;
19431        self.write(")");
19432        Ok(())
19433    }
19434
19435    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
19436        self.write_keyword("REPEAT");
19437        self.write("(");
19438        self.generate_expression(&f.this)?;
19439        self.write(", ");
19440        self.generate_expression(&f.times)?;
19441        self.write(")");
19442        Ok(())
19443    }
19444
19445    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
19446        self.write_keyword(name);
19447        self.write("(");
19448        self.generate_expression(&f.this)?;
19449        self.write(", ");
19450        self.generate_expression(&f.length)?;
19451        if let Some(fill) = &f.fill {
19452            self.write(", ");
19453            self.generate_expression(fill)?;
19454        }
19455        self.write(")");
19456        Ok(())
19457    }
19458
19459    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
19460        self.write_keyword("SPLIT");
19461        self.write("(");
19462        self.generate_expression(&f.this)?;
19463        self.write(", ");
19464        self.generate_expression(&f.delimiter)?;
19465        self.write(")");
19466        Ok(())
19467    }
19468
19469    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
19470        use crate::dialects::DialectType;
19471        // PostgreSQL uses ~ operator for regex matching
19472        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
19473            self.generate_expression(&f.this)?;
19474            self.write(" ~ ");
19475            self.generate_expression(&f.pattern)?;
19476        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
19477            // Exasol uses REGEXP_LIKE as infix binary operator
19478            self.generate_expression(&f.this)?;
19479            self.write_keyword(" REGEXP_LIKE ");
19480            self.generate_expression(&f.pattern)?;
19481        } else if matches!(
19482            self.config.dialect,
19483            Some(DialectType::SingleStore)
19484                | Some(DialectType::Spark)
19485                | Some(DialectType::Hive)
19486                | Some(DialectType::Databricks)
19487        ) && f.flags.is_none()
19488        {
19489            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
19490            self.generate_expression(&f.this)?;
19491            self.write_keyword(" RLIKE ");
19492            self.generate_expression(&f.pattern)?;
19493        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
19494            // StarRocks uses REGEXP function syntax
19495            self.write_keyword("REGEXP");
19496            self.write("(");
19497            self.generate_expression(&f.this)?;
19498            self.write(", ");
19499            self.generate_expression(&f.pattern)?;
19500            if let Some(flags) = &f.flags {
19501                self.write(", ");
19502                self.generate_expression(flags)?;
19503            }
19504            self.write(")");
19505        } else {
19506            self.write_keyword("REGEXP_LIKE");
19507            self.write("(");
19508            self.generate_expression(&f.this)?;
19509            self.write(", ");
19510            self.generate_expression(&f.pattern)?;
19511            if let Some(flags) = &f.flags {
19512                self.write(", ");
19513                self.generate_expression(flags)?;
19514            }
19515            self.write(")");
19516        }
19517        Ok(())
19518    }
19519
19520    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
19521        self.write_keyword("REGEXP_REPLACE");
19522        self.write("(");
19523        self.generate_expression(&f.this)?;
19524        self.write(", ");
19525        self.generate_expression(&f.pattern)?;
19526        self.write(", ");
19527        self.generate_expression(&f.replacement)?;
19528        if let Some(flags) = &f.flags {
19529            self.write(", ");
19530            self.generate_expression(flags)?;
19531        }
19532        self.write(")");
19533        Ok(())
19534    }
19535
19536    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
19537        self.write_keyword("REGEXP_EXTRACT");
19538        self.write("(");
19539        self.generate_expression(&f.this)?;
19540        self.write(", ");
19541        self.generate_expression(&f.pattern)?;
19542        if let Some(group) = &f.group {
19543            self.write(", ");
19544            self.generate_expression(group)?;
19545        }
19546        self.write(")");
19547        Ok(())
19548    }
19549
19550    // Math function generators
19551
19552    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
19553        self.write_keyword("ROUND");
19554        self.write("(");
19555        self.generate_expression(&f.this)?;
19556        if let Some(decimals) = &f.decimals {
19557            self.write(", ");
19558            self.generate_expression(decimals)?;
19559        }
19560        self.write(")");
19561        Ok(())
19562    }
19563
19564    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
19565        self.write_keyword("FLOOR");
19566        self.write("(");
19567        self.generate_expression(&f.this)?;
19568        // Handle Druid-style FLOOR(time TO unit) syntax
19569        if let Some(to) = &f.to {
19570            self.write(" ");
19571            self.write_keyword("TO");
19572            self.write(" ");
19573            self.generate_expression(to)?;
19574        } else if let Some(scale) = &f.scale {
19575            self.write(", ");
19576            self.generate_expression(scale)?;
19577        }
19578        self.write(")");
19579        Ok(())
19580    }
19581
19582    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
19583        self.write_keyword("CEIL");
19584        self.write("(");
19585        self.generate_expression(&f.this)?;
19586        // Handle Druid-style CEIL(time TO unit) syntax
19587        if let Some(to) = &f.to {
19588            self.write(" ");
19589            self.write_keyword("TO");
19590            self.write(" ");
19591            self.generate_expression(to)?;
19592        } else if let Some(decimals) = &f.decimals {
19593            self.write(", ");
19594            self.generate_expression(decimals)?;
19595        }
19596        self.write(")");
19597        Ok(())
19598    }
19599
19600    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
19601        use crate::expressions::Literal;
19602
19603        if let Some(base) = &f.base {
19604            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
19605            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
19606            if self.is_log_base_none() {
19607                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
19608                {
19609                    self.write_func_name("LOG2");
19610                    self.write("(");
19611                    self.generate_expression(&f.this)?;
19612                    self.write(")");
19613                    return Ok(());
19614                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
19615                {
19616                    self.write_func_name("LOG10");
19617                    self.write("(");
19618                    self.generate_expression(&f.this)?;
19619                    self.write(")");
19620                    return Ok(());
19621                }
19622                // Other bases: fall through to LOG(base, value) — best effort
19623            }
19624
19625            self.write_func_name("LOG");
19626            self.write("(");
19627            if self.is_log_value_first() {
19628                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
19629                self.generate_expression(&f.this)?;
19630                self.write(", ");
19631                self.generate_expression(base)?;
19632            } else {
19633                // Default (PostgreSQL, etc.): LOG(base, value)
19634                self.generate_expression(base)?;
19635                self.write(", ");
19636                self.generate_expression(&f.this)?;
19637            }
19638            self.write(")");
19639        } else {
19640            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
19641            self.write_func_name("LOG");
19642            self.write("(");
19643            self.generate_expression(&f.this)?;
19644            self.write(")");
19645        }
19646        Ok(())
19647    }
19648
19649    /// Whether the target dialect uses LOG(value, base) order (value first).
19650    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
19651    fn is_log_value_first(&self) -> bool {
19652        use crate::dialects::DialectType;
19653        matches!(
19654            self.config.dialect,
19655            Some(DialectType::BigQuery)
19656                | Some(DialectType::TSQL)
19657                | Some(DialectType::Tableau)
19658                | Some(DialectType::Fabric)
19659        )
19660    }
19661
19662    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
19663    /// Presto, Trino, ClickHouse, Athena.
19664    fn is_log_base_none(&self) -> bool {
19665        use crate::dialects::DialectType;
19666        matches!(
19667            self.config.dialect,
19668            Some(DialectType::Presto)
19669                | Some(DialectType::Trino)
19670                | Some(DialectType::ClickHouse)
19671                | Some(DialectType::Athena)
19672        )
19673    }
19674
19675    // Date/time function generators
19676
19677    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
19678        self.write_keyword("CURRENT_TIME");
19679        if let Some(precision) = f.precision {
19680            self.write(&format!("({})", precision));
19681        } else if matches!(
19682            self.config.dialect,
19683            Some(crate::dialects::DialectType::MySQL)
19684                | Some(crate::dialects::DialectType::SingleStore)
19685                | Some(crate::dialects::DialectType::TiDB)
19686        ) {
19687            self.write("()");
19688        }
19689        Ok(())
19690    }
19691
19692    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
19693        use crate::dialects::DialectType;
19694
19695        // Oracle/Redshift SYSDATE handling
19696        if f.sysdate {
19697            match self.config.dialect {
19698                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
19699                    self.write_keyword("SYSDATE");
19700                    return Ok(());
19701                }
19702                Some(DialectType::Snowflake) => {
19703                    // Snowflake uses SYSDATE() function
19704                    self.write_keyword("SYSDATE");
19705                    self.write("()");
19706                    return Ok(());
19707                }
19708                _ => {
19709                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
19710                }
19711            }
19712        }
19713
19714        self.write_keyword("CURRENT_TIMESTAMP");
19715        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
19716        if let Some(precision) = f.precision {
19717            self.write(&format!("({})", precision));
19718        } else if matches!(
19719            self.config.dialect,
19720            Some(crate::dialects::DialectType::MySQL)
19721                | Some(crate::dialects::DialectType::SingleStore)
19722                | Some(crate::dialects::DialectType::TiDB)
19723                | Some(crate::dialects::DialectType::Spark)
19724                | Some(crate::dialects::DialectType::Hive)
19725                | Some(crate::dialects::DialectType::Databricks)
19726                | Some(crate::dialects::DialectType::ClickHouse)
19727                | Some(crate::dialects::DialectType::BigQuery)
19728                | Some(crate::dialects::DialectType::Snowflake)
19729                | Some(crate::dialects::DialectType::Exasol)
19730        ) {
19731            self.write("()");
19732        }
19733        Ok(())
19734    }
19735
19736    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
19737        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
19738        if self.config.dialect == Some(DialectType::Exasol) {
19739            self.write_keyword("CONVERT_TZ");
19740            self.write("(");
19741            self.generate_expression(&f.this)?;
19742            self.write(", 'UTC', ");
19743            self.generate_expression(&f.zone)?;
19744            self.write(")");
19745            return Ok(());
19746        }
19747
19748        self.generate_expression(&f.this)?;
19749        self.write_space();
19750        self.write_keyword("AT TIME ZONE");
19751        self.write_space();
19752        self.generate_expression(&f.zone)?;
19753        Ok(())
19754    }
19755
19756    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
19757        use crate::dialects::DialectType;
19758
19759        // Presto/Trino use DATE_ADD('unit', interval, date) format
19760        // with the interval cast to BIGINT when needed
19761        let is_presto_like = matches!(
19762            self.config.dialect,
19763            Some(DialectType::Presto) | Some(DialectType::Trino)
19764        );
19765
19766        if is_presto_like {
19767            self.write_keyword(name);
19768            self.write("(");
19769            // Unit as string literal
19770            self.write("'");
19771            self.write_simple_interval_unit(&f.unit, false);
19772            self.write("'");
19773            self.write(", ");
19774            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
19775            let needs_cast = !self.returns_integer_type(&f.interval);
19776            if needs_cast {
19777                self.write_keyword("CAST");
19778                self.write("(");
19779            }
19780            self.generate_expression(&f.interval)?;
19781            if needs_cast {
19782                self.write_space();
19783                self.write_keyword("AS");
19784                self.write_space();
19785                self.write_keyword("BIGINT");
19786                self.write(")");
19787            }
19788            self.write(", ");
19789            self.generate_expression(&f.this)?;
19790            self.write(")");
19791        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
19792            self.generate_expression(&f.this)?;
19793            self.write_space();
19794            if name.eq_ignore_ascii_case("DATE_SUB") {
19795                self.write("-");
19796            } else {
19797                self.write("+");
19798            }
19799            self.write_space();
19800            self.write_keyword("INTERVAL");
19801            self.write_space();
19802            self.write("'");
19803            let mut interval_gen = Generator::with_arc_config(self.config.clone());
19804            let interval_sql = interval_gen.generate(&f.interval)?;
19805            self.write(&interval_sql);
19806            self.write(" ");
19807            self.write_simple_interval_unit(&f.unit, false);
19808            self.write("'");
19809        } else {
19810            self.write_keyword(name);
19811            self.write("(");
19812            self.generate_expression(&f.this)?;
19813            self.write(", ");
19814            self.write_keyword("INTERVAL");
19815            self.write_space();
19816            self.generate_expression(&f.interval)?;
19817            self.write_space();
19818            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
19819            self.write(")");
19820        }
19821        Ok(())
19822    }
19823
19824    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
19825    /// This is a heuristic to avoid full type inference
19826    fn returns_integer_type(&self, expr: &Expression) -> bool {
19827        use crate::expressions::{DataType, Literal};
19828        match expr {
19829            // Integer literals (no decimal point)
19830            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
19831                let Literal::Number(n) = lit.as_ref() else {
19832                    unreachable!()
19833                };
19834                !n.contains('.')
19835            }
19836
19837            // FLOOR(x) returns integer if x is integer
19838            Expression::Floor(f) => self.returns_integer_type(&f.this),
19839
19840            // ROUND(x) returns integer if x is integer
19841            Expression::Round(f) => {
19842                // Only if no decimals arg or it's returning an integer
19843                f.decimals.is_none() && self.returns_integer_type(&f.this)
19844            }
19845
19846            // SIGN returns integer if input is integer
19847            Expression::Sign(f) => self.returns_integer_type(&f.this),
19848
19849            // ABS returns the same type as input
19850            Expression::Abs(f) => self.returns_integer_type(&f.this),
19851
19852            // Arithmetic operations on integers return integers
19853            Expression::Mul(op) => {
19854                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19855            }
19856            Expression::Add(op) => {
19857                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19858            }
19859            Expression::Sub(op) => {
19860                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19861            }
19862            Expression::Mod(op) => self.returns_integer_type(&op.left),
19863
19864            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
19865            Expression::Cast(c) => matches!(
19866                &c.to,
19867                DataType::BigInt { .. }
19868                    | DataType::Int { .. }
19869                    | DataType::SmallInt { .. }
19870                    | DataType::TinyInt { .. }
19871            ),
19872
19873            // Negation: -x returns integer if x is integer
19874            Expression::Neg(op) => self.returns_integer_type(&op.this),
19875
19876            // Parenthesized expression
19877            Expression::Paren(p) => self.returns_integer_type(&p.this),
19878
19879            // Column references and most expressions are assumed to need casting
19880            // since we don't have full type information
19881            _ => false,
19882        }
19883    }
19884
19885    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
19886        self.write_keyword("DATEDIFF");
19887        self.write("(");
19888        if let Some(unit) = &f.unit {
19889            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
19890            self.write(", ");
19891        }
19892        if self.config.dialect == Some(DialectType::Snowflake) {
19893            self.generate_expression(&f.expression)?;
19894            self.write(", ");
19895            self.generate_expression(&f.this)?;
19896        } else {
19897            self.generate_expression(&f.this)?;
19898            self.write(", ");
19899            self.generate_expression(&f.expression)?;
19900        }
19901        self.write(")");
19902        Ok(())
19903    }
19904
19905    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
19906        if self.config.dialect == Some(DialectType::ClickHouse) {
19907            self.write("dateTrunc");
19908        } else {
19909            self.write_keyword("DATE_TRUNC");
19910        }
19911        self.write("('");
19912        self.write_datetime_field(&f.unit);
19913        self.write("', ");
19914        self.generate_expression(&f.this)?;
19915        self.write(")");
19916        Ok(())
19917    }
19918
19919    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
19920        use crate::dialects::DialectType;
19921        use crate::expressions::DateTimeField;
19922
19923        self.write_keyword("LAST_DAY");
19924        self.write("(");
19925        self.generate_expression(&f.this)?;
19926        if let Some(unit) = &f.unit {
19927            self.write(", ");
19928            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
19929            // WEEK(SUNDAY) -> WEEK
19930            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
19931                if let DateTimeField::WeekWithModifier(_) = unit {
19932                    self.write_keyword("WEEK");
19933                } else {
19934                    self.write_datetime_field(unit);
19935                }
19936            } else {
19937                self.write_datetime_field(unit);
19938            }
19939        }
19940        self.write(")");
19941        Ok(())
19942    }
19943
19944    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
19945        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
19946        if matches!(
19947            self.config.dialect,
19948            Some(DialectType::TSQL) | Some(DialectType::Fabric)
19949        ) {
19950            self.write_keyword("DATEPART");
19951            self.write("(");
19952            self.write_datetime_field(&f.field);
19953            self.write(", ");
19954            self.generate_expression(&f.this)?;
19955            self.write(")");
19956            return Ok(());
19957        }
19958        self.write_keyword("EXTRACT");
19959        self.write("(");
19960        // Hive/Spark use lowercase datetime fields in EXTRACT
19961        if matches!(
19962            self.config.dialect,
19963            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
19964        ) {
19965            self.write_datetime_field_lower(&f.field);
19966        } else {
19967            self.write_datetime_field(&f.field);
19968        }
19969        self.write_space();
19970        self.write_keyword("FROM");
19971        self.write_space();
19972        self.generate_expression(&f.this)?;
19973        self.write(")");
19974        Ok(())
19975    }
19976
19977    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
19978        self.write_keyword("TO_DATE");
19979        self.write("(");
19980        self.generate_expression(&f.this)?;
19981        if let Some(format) = &f.format {
19982            self.write(", ");
19983            self.generate_expression(format)?;
19984        }
19985        self.write(")");
19986        Ok(())
19987    }
19988
19989    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
19990        self.write_keyword("TO_TIMESTAMP");
19991        self.write("(");
19992        self.generate_expression(&f.this)?;
19993        if let Some(format) = &f.format {
19994            self.write(", ");
19995            self.generate_expression(format)?;
19996        }
19997        self.write(")");
19998        Ok(())
19999    }
20000
20001    // Control flow function generators
20002
20003    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
20004        use crate::dialects::DialectType;
20005
20006        // Generic mode: normalize IF to CASE WHEN
20007        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
20008            self.write_keyword("CASE WHEN");
20009            self.write_space();
20010            self.generate_expression(&f.condition)?;
20011            self.write_space();
20012            self.write_keyword("THEN");
20013            self.write_space();
20014            self.generate_expression(&f.true_value)?;
20015            if let Some(false_val) = &f.false_value {
20016                self.write_space();
20017                self.write_keyword("ELSE");
20018                self.write_space();
20019                self.generate_expression(false_val)?;
20020            }
20021            self.write_space();
20022            self.write_keyword("END");
20023            return Ok(());
20024        }
20025
20026        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
20027        if self.config.dialect == Some(DialectType::Exasol) {
20028            self.write_keyword("IF");
20029            self.write_space();
20030            self.generate_expression(&f.condition)?;
20031            self.write_space();
20032            self.write_keyword("THEN");
20033            self.write_space();
20034            self.generate_expression(&f.true_value)?;
20035            if let Some(false_val) = &f.false_value {
20036                self.write_space();
20037                self.write_keyword("ELSE");
20038                self.write_space();
20039                self.generate_expression(false_val)?;
20040            }
20041            self.write_space();
20042            self.write_keyword("ENDIF");
20043            return Ok(());
20044        }
20045
20046        // Choose function name based on target dialect
20047        let func_name = match self.config.dialect {
20048            Some(DialectType::ClickHouse) => f.original_name.as_deref().unwrap_or("IF"),
20049            Some(DialectType::Snowflake) => "IFF",
20050            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
20051            Some(DialectType::Drill) => "`IF`",
20052            _ => "IF",
20053        };
20054        self.write(func_name);
20055        self.write("(");
20056        self.generate_expression(&f.condition)?;
20057        self.write(", ");
20058        self.generate_expression(&f.true_value)?;
20059        if let Some(false_val) = &f.false_value {
20060            self.write(", ");
20061            self.generate_expression(false_val)?;
20062        }
20063        self.write(")");
20064        Ok(())
20065    }
20066
20067    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
20068        self.write_keyword("NVL2");
20069        self.write("(");
20070        self.generate_expression(&f.this)?;
20071        self.write(", ");
20072        self.generate_expression(&f.true_value)?;
20073        self.write(", ");
20074        self.generate_expression(&f.false_value)?;
20075        self.write(")");
20076        Ok(())
20077    }
20078
20079    // Typed aggregate function generators
20080
20081    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
20082        // Use normalize_functions for COUNT to respect ClickHouse case preservation
20083        let count_name = match self.config.normalize_functions {
20084            NormalizeFunctions::Upper => "COUNT".to_string(),
20085            NormalizeFunctions::Lower => "count".to_string(),
20086            NormalizeFunctions::None => f
20087                .original_name
20088                .clone()
20089                .unwrap_or_else(|| "COUNT".to_string()),
20090        };
20091        self.write(&count_name);
20092        self.write("(");
20093        if f.distinct {
20094            self.write_keyword("DISTINCT");
20095            self.write_space();
20096        }
20097        if f.star {
20098            self.write("*");
20099        } else if let Some(ref expr) = f.this {
20100            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
20101            if let Expression::Tuple(tuple) = expr {
20102                // Check if we need to transform multi-arg COUNT DISTINCT
20103                // When dialect doesn't support multi_arg_distinct, transform:
20104                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
20105                let needs_transform =
20106                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
20107
20108                if needs_transform {
20109                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
20110                    self.write_keyword("CASE");
20111                    for e in &tuple.expressions {
20112                        self.write_space();
20113                        self.write_keyword("WHEN");
20114                        self.write_space();
20115                        self.generate_expression(e)?;
20116                        self.write_space();
20117                        self.write_keyword("IS NULL THEN NULL");
20118                    }
20119                    self.write_space();
20120                    self.write_keyword("ELSE");
20121                    self.write(" (");
20122                    for (i, e) in tuple.expressions.iter().enumerate() {
20123                        if i > 0 {
20124                            self.write(", ");
20125                        }
20126                        self.generate_expression(e)?;
20127                    }
20128                    self.write(")");
20129                    self.write_space();
20130                    self.write_keyword("END");
20131                } else {
20132                    for (i, e) in tuple.expressions.iter().enumerate() {
20133                        if i > 0 {
20134                            self.write(", ");
20135                        }
20136                        self.generate_expression(e)?;
20137                    }
20138                }
20139            } else {
20140                self.generate_expression(expr)?;
20141            }
20142        }
20143        let clickhouse_ignore_nulls_outside =
20144            matches!(self.config.dialect, Some(DialectType::ClickHouse));
20145        if let Some(ignore) = f.ignore_nulls.filter(|_| !clickhouse_ignore_nulls_outside) {
20146            self.write_space();
20147            if ignore {
20148                self.write_keyword("IGNORE NULLS");
20149            } else {
20150                self.write_keyword("RESPECT NULLS");
20151            }
20152        }
20153        self.write(")");
20154        if let Some(ignore) = f.ignore_nulls.filter(|_| clickhouse_ignore_nulls_outside) {
20155            self.write_space();
20156            if ignore {
20157                self.write_keyword("IGNORE NULLS");
20158            } else {
20159                self.write_keyword("RESPECT NULLS");
20160            }
20161        }
20162        if let Some(ref filter) = f.filter {
20163            self.write_space();
20164            self.write_keyword("FILTER");
20165            self.write("(");
20166            self.write_keyword("WHERE");
20167            self.write_space();
20168            self.generate_expression(filter)?;
20169            self.write(")");
20170        }
20171        Ok(())
20172    }
20173
20174    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
20175        // Apply function name normalization based on config
20176        let func_name: Cow<'_, str> = match self.config.normalize_functions {
20177            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
20178            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
20179            NormalizeFunctions::None => {
20180                // Use the original function name from parsing if available,
20181                // otherwise fall back to lowercase of the hardcoded constant
20182                if let Some(ref original) = f.name {
20183                    Cow::Owned(original.clone())
20184                } else {
20185                    Cow::Owned(name.to_ascii_lowercase())
20186                }
20187            }
20188        };
20189        self.write(func_name.as_ref());
20190        self.write("(");
20191        if f.distinct {
20192            self.write_keyword("DISTINCT");
20193            self.write_space();
20194        }
20195        // MODE() uses a NULL placeholder internally for its zero-arg ordered-set form.
20196        // Other aggregates may legitimately receive NULL as an explicit argument.
20197        let is_zero_arg_mode =
20198            name.eq_ignore_ascii_case("MODE") && matches!(f.this, Expression::Null(_));
20199        if !is_zero_arg_mode {
20200            self.generate_expression(&f.this)?;
20201        }
20202        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
20203        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
20204        if self.config.ignore_nulls_in_func
20205            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
20206        {
20207            match f.ignore_nulls {
20208                Some(true) => {
20209                    self.write_space();
20210                    self.write_keyword("IGNORE NULLS");
20211                }
20212                Some(false) => {
20213                    self.write_space();
20214                    self.write_keyword("RESPECT NULLS");
20215                }
20216                None => {}
20217            }
20218        }
20219        // Generate HAVING MAX/MIN if present (BigQuery syntax)
20220        // e.g., ANY_VALUE(fruit HAVING MAX sold)
20221        if let Some((ref expr, is_max)) = f.having_max {
20222            self.write_space();
20223            self.write_keyword("HAVING");
20224            self.write_space();
20225            if is_max {
20226                self.write_keyword("MAX");
20227            } else {
20228                self.write_keyword("MIN");
20229            }
20230            self.write_space();
20231            self.generate_expression(expr)?;
20232        }
20233        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
20234        if !f.order_by.is_empty() {
20235            self.write_space();
20236            self.write_keyword("ORDER BY");
20237            self.write_space();
20238            for (i, ord) in f.order_by.iter().enumerate() {
20239                if i > 0 {
20240                    self.write(", ");
20241                }
20242                self.generate_ordered(ord)?;
20243            }
20244        }
20245        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
20246        if let Some(ref limit) = f.limit {
20247            self.write_space();
20248            self.write_keyword("LIMIT");
20249            self.write_space();
20250            // Check if this is a Tuple representing LIMIT offset, count
20251            if let Expression::Tuple(t) = limit.as_ref() {
20252                if t.expressions.len() == 2 {
20253                    self.generate_expression(&t.expressions[0])?;
20254                    self.write(", ");
20255                    self.generate_expression(&t.expressions[1])?;
20256                } else {
20257                    self.generate_expression(limit)?;
20258                }
20259            } else {
20260                self.generate_expression(limit)?;
20261            }
20262        }
20263        self.write(")");
20264        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
20265        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
20266        if !self.config.ignore_nulls_in_func
20267            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
20268        {
20269            match f.ignore_nulls {
20270                Some(true) => {
20271                    self.write_space();
20272                    self.write_keyword("IGNORE NULLS");
20273                }
20274                Some(false) => {
20275                    self.write_space();
20276                    self.write_keyword("RESPECT NULLS");
20277                }
20278                None => {}
20279            }
20280        }
20281        if let Some(ref filter) = f.filter {
20282            self.write_space();
20283            self.write_keyword("FILTER");
20284            self.write("(");
20285            self.write_keyword("WHERE");
20286            self.write_space();
20287            self.generate_expression(filter)?;
20288            self.write(")");
20289        }
20290        Ok(())
20291    }
20292
20293    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
20294    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
20295    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
20296        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
20297        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
20298            // Create a modified copy without ignore_nulls, add TRUE as part of the output
20299            let func_name: Cow<'_, str> = match self.config.normalize_functions {
20300                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
20301                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
20302                NormalizeFunctions::None => {
20303                    if let Some(ref original) = f.name {
20304                        Cow::Owned(original.clone())
20305                    } else {
20306                        Cow::Owned(name.to_ascii_lowercase())
20307                    }
20308                }
20309            };
20310            self.write(func_name.as_ref());
20311            self.write("(");
20312            if f.distinct {
20313                self.write_keyword("DISTINCT");
20314                self.write_space();
20315            }
20316            if !matches!(f.this, Expression::Null(_)) {
20317                self.generate_expression(&f.this)?;
20318            }
20319            self.write(", ");
20320            self.write_keyword("TRUE");
20321            self.write(")");
20322            return Ok(());
20323        }
20324        self.generate_agg_func(name, f)
20325    }
20326
20327    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
20328        self.write_keyword("GROUP_CONCAT");
20329        self.write("(");
20330        if f.distinct {
20331            self.write_keyword("DISTINCT");
20332            self.write_space();
20333        }
20334        self.generate_expression(&f.this)?;
20335        if let Some(ref order_by) = f.order_by {
20336            self.write_space();
20337            self.write_keyword("ORDER BY");
20338            self.write_space();
20339            for (i, ord) in order_by.iter().enumerate() {
20340                if i > 0 {
20341                    self.write(", ");
20342                }
20343                self.generate_ordered(ord)?;
20344            }
20345        }
20346        if let Some(ref sep) = f.separator {
20347            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
20348            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
20349            if matches!(
20350                self.config.dialect,
20351                Some(crate::dialects::DialectType::SQLite)
20352            ) {
20353                self.write(", ");
20354                self.generate_expression(sep)?;
20355            } else {
20356                self.write_space();
20357                self.write_keyword("SEPARATOR");
20358                self.write_space();
20359                self.generate_expression(sep)?;
20360            }
20361        }
20362        if let Some(ref limit) = f.limit {
20363            self.write_space();
20364            self.write_keyword("LIMIT");
20365            self.write_space();
20366            self.generate_expression(limit)?;
20367        }
20368        self.write(")");
20369        if let Some(ref filter) = f.filter {
20370            self.write_space();
20371            self.write_keyword("FILTER");
20372            self.write("(");
20373            self.write_keyword("WHERE");
20374            self.write_space();
20375            self.generate_expression(filter)?;
20376            self.write(")");
20377        }
20378        Ok(())
20379    }
20380
20381    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
20382        let uses_within_group_order = matches!(
20383            self.config.dialect,
20384            Some(crate::dialects::DialectType::TSQL | crate::dialects::DialectType::Fabric)
20385        );
20386        self.write_keyword("STRING_AGG");
20387        self.write("(");
20388        if f.distinct {
20389            self.write_keyword("DISTINCT");
20390            self.write_space();
20391        }
20392        self.generate_expression(&f.this)?;
20393        if let Some(ref separator) = f.separator {
20394            self.write(", ");
20395            self.generate_expression(separator)?;
20396        }
20397        // TSQL/Fabric put aggregate ORDER BY in WITHIN GROUP after the closing paren.
20398        if !uses_within_group_order {
20399            if let Some(ref order_by) = f.order_by {
20400                self.write_space();
20401                self.write_keyword("ORDER BY");
20402                self.write_space();
20403                for (i, ord) in order_by.iter().enumerate() {
20404                    if i > 0 {
20405                        self.write(", ");
20406                    }
20407                    self.generate_ordered(ord)?;
20408                }
20409            }
20410        }
20411        if let Some(ref limit) = f.limit {
20412            self.write_space();
20413            self.write_keyword("LIMIT");
20414            self.write_space();
20415            self.generate_expression(limit)?;
20416        }
20417        self.write(")");
20418        if uses_within_group_order {
20419            if let Some(ref order_by) = f.order_by {
20420                self.write_space();
20421                self.write_keyword("WITHIN GROUP");
20422                self.write(" (");
20423                self.write_keyword("ORDER BY");
20424                self.write_space();
20425                for (i, ord) in order_by.iter().enumerate() {
20426                    if i > 0 {
20427                        self.write(", ");
20428                    }
20429                    self.generate_ordered(ord)?;
20430                }
20431                self.write(")");
20432            }
20433        }
20434        if let Some(ref filter) = f.filter {
20435            self.write_space();
20436            self.write_keyword("FILTER");
20437            self.write("(");
20438            self.write_keyword("WHERE");
20439            self.write_space();
20440            self.generate_expression(filter)?;
20441            self.write(")");
20442        }
20443        Ok(())
20444    }
20445
20446    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
20447        use crate::dialects::DialectType;
20448        let order_inside_args = matches!(self.config.dialect, Some(DialectType::DuckDB));
20449        self.write_keyword("LISTAGG");
20450        self.write("(");
20451        if f.distinct {
20452            self.write_keyword("DISTINCT");
20453            self.write_space();
20454        }
20455        self.generate_expression(&f.this)?;
20456        if let Some(ref sep) = f.separator {
20457            self.write(", ");
20458            self.generate_expression(sep)?;
20459        } else if matches!(
20460            self.config.dialect,
20461            Some(DialectType::Trino) | Some(DialectType::Presto)
20462        ) {
20463            // Trino/Presto require explicit separator; default to ','
20464            self.write(", ','");
20465        }
20466        if let Some(ref overflow) = f.on_overflow {
20467            self.write_space();
20468            self.write_keyword("ON OVERFLOW");
20469            self.write_space();
20470            match overflow {
20471                ListAggOverflow::Error => self.write_keyword("ERROR"),
20472                ListAggOverflow::Truncate { filler, with_count } => {
20473                    self.write_keyword("TRUNCATE");
20474                    if let Some(ref fill) = filler {
20475                        self.write_space();
20476                        self.generate_expression(fill)?;
20477                    }
20478                    if *with_count {
20479                        self.write_space();
20480                        self.write_keyword("WITH COUNT");
20481                    } else {
20482                        self.write_space();
20483                        self.write_keyword("WITHOUT COUNT");
20484                    }
20485                }
20486            }
20487        }
20488        if order_inside_args {
20489            if let Some(ref order_by) = f.order_by {
20490                self.write_space();
20491                self.write_keyword("ORDER BY");
20492                self.write_space();
20493                for (i, ord) in order_by.iter().enumerate() {
20494                    if i > 0 {
20495                        self.write(", ");
20496                    }
20497                    self.generate_ordered(ord)?;
20498                }
20499            }
20500        }
20501        self.write(")");
20502        if !order_inside_args {
20503            if let Some(ref order_by) = f.order_by {
20504                self.write_space();
20505                self.write_keyword("WITHIN GROUP");
20506                self.write(" (");
20507                self.write_keyword("ORDER BY");
20508                self.write_space();
20509                for (i, ord) in order_by.iter().enumerate() {
20510                    if i > 0 {
20511                        self.write(", ");
20512                    }
20513                    self.generate_ordered(ord)?;
20514                }
20515                self.write(")");
20516            }
20517        }
20518        if let Some(ref filter) = f.filter {
20519            self.write_space();
20520            self.write_keyword("FILTER");
20521            self.write("(");
20522            self.write_keyword("WHERE");
20523            self.write_space();
20524            self.generate_expression(filter)?;
20525            self.write(")");
20526        }
20527        Ok(())
20528    }
20529
20530    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
20531        self.write_keyword("SUM_IF");
20532        self.write("(");
20533        self.generate_expression(&f.this)?;
20534        self.write(", ");
20535        self.generate_expression(&f.condition)?;
20536        self.write(")");
20537        if let Some(ref filter) = f.filter {
20538            self.write_space();
20539            self.write_keyword("FILTER");
20540            self.write("(");
20541            self.write_keyword("WHERE");
20542            self.write_space();
20543            self.generate_expression(filter)?;
20544            self.write(")");
20545        }
20546        Ok(())
20547    }
20548
20549    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
20550        self.write_keyword("APPROX_PERCENTILE");
20551        self.write("(");
20552        self.generate_expression(&f.this)?;
20553        self.write(", ");
20554        self.generate_expression(&f.percentile)?;
20555        if let Some(ref acc) = f.accuracy {
20556            self.write(", ");
20557            self.generate_expression(acc)?;
20558        }
20559        self.write(")");
20560        if let Some(ref filter) = f.filter {
20561            self.write_space();
20562            self.write_keyword("FILTER");
20563            self.write("(");
20564            self.write_keyword("WHERE");
20565            self.write_space();
20566            self.generate_expression(filter)?;
20567            self.write(")");
20568        }
20569        Ok(())
20570    }
20571
20572    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
20573        self.write_keyword(name);
20574        self.write("(");
20575        self.generate_expression(&f.percentile)?;
20576        self.write(")");
20577        if let Some(ref order_by) = f.order_by {
20578            self.write_space();
20579            self.write_keyword("WITHIN GROUP");
20580            self.write(" (");
20581            self.write_keyword("ORDER BY");
20582            self.write_space();
20583            self.generate_expression(&f.this)?;
20584            for ord in order_by.iter() {
20585                if ord.desc {
20586                    self.write_space();
20587                    self.write_keyword("DESC");
20588                }
20589            }
20590            self.write(")");
20591        }
20592        if let Some(ref filter) = f.filter {
20593            self.write_space();
20594            self.write_keyword("FILTER");
20595            self.write("(");
20596            self.write_keyword("WHERE");
20597            self.write_space();
20598            self.generate_expression(filter)?;
20599            self.write(")");
20600        }
20601        Ok(())
20602    }
20603
20604    // Window function generators
20605
20606    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
20607        self.write_keyword("NTILE");
20608        self.write("(");
20609        if let Some(num_buckets) = &f.num_buckets {
20610            self.generate_expression(num_buckets)?;
20611        }
20612        if let Some(order_by) = &f.order_by {
20613            self.write_keyword(" ORDER BY ");
20614            for (i, ob) in order_by.iter().enumerate() {
20615                if i > 0 {
20616                    self.write(", ");
20617                }
20618                self.generate_ordered(ob)?;
20619            }
20620        }
20621        self.write(")");
20622        Ok(())
20623    }
20624
20625    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
20626        self.write_keyword(name);
20627        self.write("(");
20628        self.generate_expression(&f.this)?;
20629        if let Some(ref offset) = f.offset {
20630            self.write(", ");
20631            self.generate_expression(offset)?;
20632            if let Some(ref default) = f.default {
20633                self.write(", ");
20634                self.generate_expression(default)?;
20635            }
20636        }
20637        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
20638        if self.config.ignore_nulls_in_func {
20639            match f.ignore_nulls {
20640                Some(true) => {
20641                    self.write_space();
20642                    self.write_keyword("IGNORE NULLS");
20643                }
20644                Some(false) => {
20645                    self.write_space();
20646                    self.write_keyword("RESPECT NULLS");
20647                }
20648                None => {}
20649            }
20650        }
20651        self.write(")");
20652        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20653        if !self.config.ignore_nulls_in_func {
20654            match f.ignore_nulls {
20655                Some(true) => {
20656                    self.write_space();
20657                    self.write_keyword("IGNORE NULLS");
20658                }
20659                Some(false) => {
20660                    self.write_space();
20661                    self.write_keyword("RESPECT NULLS");
20662                }
20663                None => {}
20664            }
20665        }
20666        Ok(())
20667    }
20668
20669    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
20670        self.write_keyword(name);
20671        self.write("(");
20672        self.generate_expression(&f.this)?;
20673        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
20674        if !f.order_by.is_empty() {
20675            self.write_space();
20676            self.write_keyword("ORDER BY");
20677            self.write_space();
20678            for (i, ordered) in f.order_by.iter().enumerate() {
20679                if i > 0 {
20680                    self.write(", ");
20681                }
20682                self.generate_ordered(ordered)?;
20683            }
20684        }
20685        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20686        if self.config.ignore_nulls_in_func {
20687            match f.ignore_nulls {
20688                Some(true) => {
20689                    self.write_space();
20690                    self.write_keyword("IGNORE NULLS");
20691                }
20692                Some(false) => {
20693                    self.write_space();
20694                    self.write_keyword("RESPECT NULLS");
20695                }
20696                None => {}
20697            }
20698        }
20699        self.write(")");
20700        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20701        if !self.config.ignore_nulls_in_func {
20702            match f.ignore_nulls {
20703                Some(true) => {
20704                    self.write_space();
20705                    self.write_keyword("IGNORE NULLS");
20706                }
20707                Some(false) => {
20708                    self.write_space();
20709                    self.write_keyword("RESPECT NULLS");
20710                }
20711                None => {}
20712            }
20713        }
20714        Ok(())
20715    }
20716
20717    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
20718    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
20719    fn generate_value_func_with_ignore_nulls_bool(
20720        &mut self,
20721        name: &str,
20722        f: &ValueFunc,
20723    ) -> Result<()> {
20724        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
20725            self.write_keyword(name);
20726            self.write("(");
20727            self.generate_expression(&f.this)?;
20728            self.write(", ");
20729            self.write_keyword("TRUE");
20730            self.write(")");
20731            return Ok(());
20732        }
20733        self.generate_value_func(name, f)
20734    }
20735
20736    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
20737        self.write_keyword("NTH_VALUE");
20738        self.write("(");
20739        self.generate_expression(&f.this)?;
20740        self.write(", ");
20741        self.generate_expression(&f.offset)?;
20742        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20743        if self.config.ignore_nulls_in_func {
20744            match f.ignore_nulls {
20745                Some(true) => {
20746                    self.write_space();
20747                    self.write_keyword("IGNORE NULLS");
20748                }
20749                Some(false) => {
20750                    self.write_space();
20751                    self.write_keyword("RESPECT NULLS");
20752                }
20753                None => {}
20754            }
20755        }
20756        self.write(")");
20757        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
20758        if matches!(
20759            self.config.dialect,
20760            Some(crate::dialects::DialectType::Snowflake)
20761        ) {
20762            match f.from_first {
20763                Some(true) => {
20764                    self.write_space();
20765                    self.write_keyword("FROM FIRST");
20766                }
20767                Some(false) => {
20768                    self.write_space();
20769                    self.write_keyword("FROM LAST");
20770                }
20771                None => {}
20772            }
20773        }
20774        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20775        if !self.config.ignore_nulls_in_func {
20776            match f.ignore_nulls {
20777                Some(true) => {
20778                    self.write_space();
20779                    self.write_keyword("IGNORE NULLS");
20780                }
20781                Some(false) => {
20782                    self.write_space();
20783                    self.write_keyword("RESPECT NULLS");
20784                }
20785                None => {}
20786            }
20787        }
20788        Ok(())
20789    }
20790
20791    // Additional string function generators
20792
20793    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
20794        // Standard syntax: POSITION(substr IN str)
20795        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
20796        if matches!(
20797            self.config.dialect,
20798            Some(crate::dialects::DialectType::ClickHouse)
20799        ) {
20800            self.write_keyword("POSITION");
20801            self.write("(");
20802            self.generate_expression(&f.string)?;
20803            self.write(", ");
20804            self.generate_expression(&f.substring)?;
20805            if let Some(ref start) = f.start {
20806                self.write(", ");
20807                self.generate_expression(start)?;
20808            }
20809            self.write(")");
20810            return Ok(());
20811        }
20812
20813        self.write_keyword("POSITION");
20814        self.write("(");
20815        self.generate_expression(&f.substring)?;
20816        self.write_space();
20817        self.write_keyword("IN");
20818        self.write_space();
20819        self.generate_expression(&f.string)?;
20820        if let Some(ref start) = f.start {
20821            self.write(", ");
20822            self.generate_expression(start)?;
20823        }
20824        self.write(")");
20825        Ok(())
20826    }
20827
20828    // Additional math function generators
20829
20830    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
20831        // Teradata RANDOM(lower, upper)
20832        if f.lower.is_some() || f.upper.is_some() {
20833            self.write_keyword("RANDOM");
20834            self.write("(");
20835            if let Some(ref lower) = f.lower {
20836                self.generate_expression(lower)?;
20837            }
20838            if let Some(ref upper) = f.upper {
20839                self.write(", ");
20840                self.generate_expression(upper)?;
20841            }
20842            self.write(")");
20843            return Ok(());
20844        }
20845        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
20846        let func_name = match self.config.dialect {
20847            Some(crate::dialects::DialectType::Snowflake)
20848            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
20849            _ => "RAND",
20850        };
20851        self.write_keyword(func_name);
20852        self.write("(");
20853        // DuckDB doesn't support seeded RANDOM, so skip the seed
20854        if !matches!(
20855            self.config.dialect,
20856            Some(crate::dialects::DialectType::DuckDB)
20857        ) {
20858            if let Some(ref seed) = f.seed {
20859                self.generate_expression(seed)?;
20860            }
20861        }
20862        self.write(")");
20863        Ok(())
20864    }
20865
20866    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
20867        self.write_keyword("TRUNCATE");
20868        self.write("(");
20869        self.generate_expression(&f.this)?;
20870        if let Some(ref decimals) = f.decimals {
20871            self.write(", ");
20872            self.generate_expression(decimals)?;
20873        }
20874        self.write(")");
20875        Ok(())
20876    }
20877
20878    // Control flow generators
20879
20880    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
20881        self.write_keyword("DECODE");
20882        self.write("(");
20883        self.generate_expression(&f.this)?;
20884        for (search, result) in &f.search_results {
20885            self.write(", ");
20886            self.generate_expression(search)?;
20887            self.write(", ");
20888            self.generate_expression(result)?;
20889        }
20890        if let Some(ref default) = f.default {
20891            self.write(", ");
20892            self.generate_expression(default)?;
20893        }
20894        self.write(")");
20895        Ok(())
20896    }
20897
20898    // Date/time function generators
20899
20900    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
20901        self.write_keyword(name);
20902        self.write("(");
20903        self.generate_expression(&f.this)?;
20904        self.write(", ");
20905        self.generate_expression(&f.format)?;
20906        self.write(")");
20907        Ok(())
20908    }
20909
20910    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
20911        self.write_keyword("FROM_UNIXTIME");
20912        self.write("(");
20913        self.generate_expression(&f.this)?;
20914        if let Some(ref format) = f.format {
20915            self.write(", ");
20916            self.generate_expression(format)?;
20917        }
20918        self.write(")");
20919        Ok(())
20920    }
20921
20922    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
20923        self.write_keyword("UNIX_TIMESTAMP");
20924        self.write("(");
20925        if let Some(ref expr) = f.this {
20926            self.generate_expression(expr)?;
20927            if let Some(ref format) = f.format {
20928                self.write(", ");
20929                self.generate_expression(format)?;
20930            }
20931        } else if matches!(
20932            self.config.dialect,
20933            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
20934        ) {
20935            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
20936            self.write_keyword("CURRENT_TIMESTAMP");
20937            self.write("()");
20938        }
20939        self.write(")");
20940        Ok(())
20941    }
20942
20943    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
20944        self.write_keyword("MAKE_DATE");
20945        self.write("(");
20946        self.generate_expression(&f.year)?;
20947        self.write(", ");
20948        self.generate_expression(&f.month)?;
20949        self.write(", ");
20950        self.generate_expression(&f.day)?;
20951        self.write(")");
20952        Ok(())
20953    }
20954
20955    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
20956        self.write_keyword("MAKE_TIMESTAMP");
20957        self.write("(");
20958        self.generate_expression(&f.year)?;
20959        self.write(", ");
20960        self.generate_expression(&f.month)?;
20961        self.write(", ");
20962        self.generate_expression(&f.day)?;
20963        self.write(", ");
20964        self.generate_expression(&f.hour)?;
20965        self.write(", ");
20966        self.generate_expression(&f.minute)?;
20967        self.write(", ");
20968        self.generate_expression(&f.second)?;
20969        if let Some(ref tz) = f.timezone {
20970            self.write(", ");
20971            self.generate_expression(tz)?;
20972        }
20973        self.write(")");
20974        Ok(())
20975    }
20976
20977    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
20978    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
20979        match expr {
20980            Expression::Struct(s) => {
20981                if s.fields.iter().all(|(name, _)| name.is_some()) {
20982                    Some(
20983                        s.fields
20984                            .iter()
20985                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
20986                            .collect(),
20987                    )
20988                } else {
20989                    None
20990                }
20991            }
20992            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20993                // Check if all args are Alias (named fields)
20994                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
20995                    Some(
20996                        f.args
20997                            .iter()
20998                            .filter_map(|a| {
20999                                if let Expression::Alias(alias) = a {
21000                                    Some(alias.alias.name.clone())
21001                                } else {
21002                                    None
21003                                }
21004                            })
21005                            .collect(),
21006                    )
21007                } else {
21008                    None
21009                }
21010            }
21011            _ => None,
21012        }
21013    }
21014
21015    /// Check if a struct expression has any unnamed fields
21016    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
21017        match expr {
21018            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
21019            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
21020                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
21021            }
21022            _ => false,
21023        }
21024    }
21025
21026    /// Get the field count of a struct expression
21027    fn struct_field_count(expr: &Expression) -> usize {
21028        match expr {
21029            Expression::Struct(s) => s.fields.len(),
21030            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
21031            _ => 0,
21032        }
21033    }
21034
21035    /// Apply field names to an unnamed struct expression, producing a new expression with names
21036    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
21037        match expr {
21038            Expression::Struct(s) => {
21039                let mut new_fields = Vec::with_capacity(s.fields.len());
21040                for (i, (name, value)) in s.fields.iter().enumerate() {
21041                    if name.is_none() && i < field_names.len() {
21042                        new_fields.push((Some(field_names[i].clone()), value.clone()));
21043                    } else {
21044                        new_fields.push((name.clone(), value.clone()));
21045                    }
21046                }
21047                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
21048            }
21049            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
21050                let mut new_args = Vec::with_capacity(f.args.len());
21051                for (i, arg) in f.args.iter().enumerate() {
21052                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
21053                        // Wrap the value in an Alias with the inherited name
21054                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
21055                            this: arg.clone(),
21056                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
21057                            column_aliases: Vec::new(),
21058                            alias_explicit_as: false,
21059                            alias_keyword: None,
21060                            pre_alias_comments: Vec::new(),
21061                            trailing_comments: Vec::new(),
21062                            inferred_type: None,
21063                        })));
21064                    } else {
21065                        new_args.push(arg.clone());
21066                    }
21067                }
21068                Expression::Function(Box::new(crate::expressions::Function {
21069                    name: f.name.clone(),
21070                    args: new_args,
21071                    distinct: f.distinct,
21072                    trailing_comments: f.trailing_comments.clone(),
21073                    use_bracket_syntax: f.use_bracket_syntax,
21074                    no_parens: f.no_parens,
21075                    quoted: f.quoted,
21076                    span: None,
21077                    inferred_type: None,
21078                }))
21079            }
21080            _ => expr.clone(),
21081        }
21082    }
21083
21084    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
21085    /// This implements BigQuery's implicit field name inheritance for struct arrays.
21086    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
21087    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
21088        let first = match expressions.first() {
21089            Some(e) => e,
21090            None => return expressions.to_vec(),
21091        };
21092
21093        let field_names = match Self::extract_struct_field_names(first) {
21094            Some(names) if !names.is_empty() => names,
21095            _ => return expressions.to_vec(),
21096        };
21097
21098        let mut result = Vec::with_capacity(expressions.len());
21099        for (idx, expr) in expressions.iter().enumerate() {
21100            if idx == 0 {
21101                result.push(expr.clone());
21102                continue;
21103            }
21104            // Check if this is a struct with unnamed fields that needs name propagation
21105            if Self::struct_field_count(expr) == field_names.len()
21106                && Self::struct_has_unnamed_fields(expr)
21107            {
21108                result.push(Self::apply_struct_field_names(expr, &field_names));
21109            } else {
21110                result.push(expr.clone());
21111            }
21112        }
21113        result
21114    }
21115
21116    // Array function generators
21117
21118    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
21119        // Apply struct name inheritance for target dialects that need it
21120        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
21121        let needs_inheritance = matches!(
21122            self.config.dialect,
21123            Some(DialectType::DuckDB)
21124                | Some(DialectType::Spark)
21125                | Some(DialectType::Databricks)
21126                | Some(DialectType::Hive)
21127                | Some(DialectType::Snowflake)
21128                | Some(DialectType::Presto)
21129                | Some(DialectType::Trino)
21130        );
21131        let propagated: Vec<Expression>;
21132        let expressions = if needs_inheritance && f.expressions.len() > 1 {
21133            propagated = Self::inherit_struct_field_names(&f.expressions);
21134            &propagated
21135        } else {
21136            &f.expressions
21137        };
21138
21139        // Check if elements should be split onto multiple lines (pretty + too wide)
21140        let should_split = if self.config.pretty && !expressions.is_empty() {
21141            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
21142            for expr in expressions {
21143                let mut temp_gen = Generator::with_arc_config(self.config.clone());
21144                Arc::make_mut(&mut temp_gen.config).pretty = false;
21145                temp_gen.generate_expression(expr)?;
21146                expr_strings.push(temp_gen.output);
21147            }
21148            self.too_wide(&expr_strings)
21149        } else {
21150            false
21151        };
21152
21153        if f.bracket_notation {
21154            // For Spark/Databricks, use ARRAY(...) with parens
21155            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
21156            // For others (DuckDB, Snowflake), use bare [...]
21157            let (open, close) = match self.config.dialect {
21158                None
21159                | Some(DialectType::Generic)
21160                | Some(DialectType::Spark)
21161                | Some(DialectType::Databricks)
21162                | Some(DialectType::Hive) => {
21163                    self.write_keyword("ARRAY");
21164                    ("(", ")")
21165                }
21166                Some(DialectType::Presto)
21167                | Some(DialectType::Trino)
21168                | Some(DialectType::PostgreSQL)
21169                | Some(DialectType::Redshift)
21170                | Some(DialectType::Materialize)
21171                | Some(DialectType::RisingWave)
21172                | Some(DialectType::CockroachDB) => {
21173                    self.write_keyword("ARRAY");
21174                    ("[", "]")
21175                }
21176                _ => ("[", "]"),
21177            };
21178            self.write(open);
21179            if should_split {
21180                self.write_newline();
21181                self.indent_level += 1;
21182                for (i, expr) in expressions.iter().enumerate() {
21183                    self.write_indent();
21184                    self.generate_expression(expr)?;
21185                    if i + 1 < expressions.len() {
21186                        self.write(",");
21187                    }
21188                    self.write_newline();
21189                }
21190                self.indent_level -= 1;
21191                self.write_indent();
21192            } else {
21193                for (i, expr) in expressions.iter().enumerate() {
21194                    if i > 0 {
21195                        self.write(", ");
21196                    }
21197                    self.generate_expression(expr)?;
21198                }
21199            }
21200            self.write(close);
21201        } else {
21202            // Use LIST keyword if that was the original syntax (DuckDB)
21203            if f.use_list_keyword {
21204                self.write_keyword("LIST");
21205            } else {
21206                self.write_keyword("ARRAY");
21207            }
21208            // For Spark/Hive, always use ARRAY(...) with parens
21209            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
21210            let has_subquery = expressions
21211                .iter()
21212                .any(|e| matches!(e, Expression::Select(_)));
21213            let (open, close) = if matches!(
21214                self.config.dialect,
21215                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
21216            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
21217                && has_subquery)
21218            {
21219                ("(", ")")
21220            } else {
21221                ("[", "]")
21222            };
21223            self.write(open);
21224            if should_split {
21225                self.write_newline();
21226                self.indent_level += 1;
21227                for (i, expr) in expressions.iter().enumerate() {
21228                    self.write_indent();
21229                    self.generate_expression(expr)?;
21230                    if i + 1 < expressions.len() {
21231                        self.write(",");
21232                    }
21233                    self.write_newline();
21234                }
21235                self.indent_level -= 1;
21236                self.write_indent();
21237            } else {
21238                for (i, expr) in expressions.iter().enumerate() {
21239                    if i > 0 {
21240                        self.write(", ");
21241                    }
21242                    self.generate_expression(expr)?;
21243                }
21244            }
21245            self.write(close);
21246        }
21247        Ok(())
21248    }
21249
21250    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
21251        self.write_keyword("ARRAY_SORT");
21252        self.write("(");
21253        self.generate_expression(&f.this)?;
21254        if let Some(ref comp) = f.comparator {
21255            self.write(", ");
21256            self.generate_expression(comp)?;
21257        }
21258        self.write(")");
21259        Ok(())
21260    }
21261
21262    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
21263        self.write_keyword(name);
21264        self.write("(");
21265        self.generate_expression(&f.this)?;
21266        self.write(", ");
21267        self.generate_expression(&f.separator)?;
21268        if let Some(ref null_rep) = f.null_replacement {
21269            self.write(", ");
21270            self.generate_expression(null_rep)?;
21271        }
21272        self.write(")");
21273        Ok(())
21274    }
21275
21276    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
21277        self.write_keyword("UNNEST");
21278        self.write("(");
21279        self.generate_expression(&f.this)?;
21280        for extra in &f.expressions {
21281            self.write(", ");
21282            self.generate_expression(extra)?;
21283        }
21284        self.write(")");
21285        if f.with_ordinality {
21286            self.write_space();
21287            if self.config.unnest_with_ordinality {
21288                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
21289                self.write_keyword("WITH ORDINALITY");
21290            } else if f.offset_alias.is_some() {
21291                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
21292                // Alias (if any) comes BEFORE WITH OFFSET
21293                if let Some(ref alias) = f.alias {
21294                    self.write_keyword("AS");
21295                    self.write_space();
21296                    self.generate_identifier(alias)?;
21297                    self.write_space();
21298                }
21299                self.write_keyword("WITH OFFSET");
21300                if let Some(ref offset_alias) = f.offset_alias {
21301                    self.write_space();
21302                    self.write_keyword("AS");
21303                    self.write_space();
21304                    self.generate_identifier(offset_alias)?;
21305                }
21306            } else {
21307                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
21308                self.write_keyword("WITH OFFSET");
21309                if f.alias.is_none() {
21310                    self.write(" AS offset");
21311                }
21312            }
21313        }
21314        if let Some(ref alias) = f.alias {
21315            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
21316            let should_add_alias = if !f.with_ordinality {
21317                true
21318            } else if self.config.unnest_with_ordinality {
21319                // Presto/Trino: alias comes after WITH ORDINALITY
21320                true
21321            } else if f.offset_alias.is_some() {
21322                // BigQuery expansion: alias already handled above
21323                false
21324            } else {
21325                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
21326                true
21327            };
21328            if should_add_alias {
21329                self.write_space();
21330                self.write_keyword("AS");
21331                self.write_space();
21332                self.generate_identifier(alias)?;
21333            }
21334        }
21335        Ok(())
21336    }
21337
21338    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
21339        self.write_keyword("FILTER");
21340        self.write("(");
21341        self.generate_expression(&f.this)?;
21342        self.write(", ");
21343        self.generate_expression(&f.filter)?;
21344        self.write(")");
21345        Ok(())
21346    }
21347
21348    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
21349        self.write_keyword("TRANSFORM");
21350        self.write("(");
21351        self.generate_expression(&f.this)?;
21352        self.write(", ");
21353        self.generate_expression(&f.transform)?;
21354        self.write(")");
21355        Ok(())
21356    }
21357
21358    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
21359        self.write_keyword(name);
21360        self.write("(");
21361        self.generate_expression(&f.start)?;
21362        self.write(", ");
21363        self.generate_expression(&f.stop)?;
21364        if let Some(ref step) = f.step {
21365            self.write(", ");
21366            self.generate_expression(step)?;
21367        }
21368        self.write(")");
21369        Ok(())
21370    }
21371
21372    // Struct function generators
21373
21374    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
21375        self.write_keyword("STRUCT");
21376        self.write("(");
21377        for (i, (name, expr)) in f.fields.iter().enumerate() {
21378            if i > 0 {
21379                self.write(", ");
21380            }
21381            if let Some(ref id) = name {
21382                self.generate_identifier(id)?;
21383                self.write(" ");
21384                self.write_keyword("AS");
21385                self.write(" ");
21386            }
21387            self.generate_expression(expr)?;
21388        }
21389        self.write(")");
21390        Ok(())
21391    }
21392
21393    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
21394    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
21395        // Extract named/unnamed fields from function args
21396        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
21397        let mut names: Vec<Option<String>> = Vec::new();
21398        let mut values: Vec<&Expression> = Vec::new();
21399        let mut all_named = true;
21400
21401        for arg in &func.args {
21402            match arg {
21403                Expression::Alias(a) => {
21404                    names.push(Some(a.alias.name.clone()));
21405                    values.push(&a.this);
21406                }
21407                _ => {
21408                    names.push(None);
21409                    values.push(arg);
21410                    all_named = false;
21411                }
21412            }
21413        }
21414
21415        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
21416            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
21417            self.write("{");
21418            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21419                if i > 0 {
21420                    self.write(", ");
21421                }
21422                if let Some(n) = name {
21423                    self.write("'");
21424                    self.write(n);
21425                    self.write("'");
21426                } else {
21427                    self.write("'_");
21428                    self.write(&i.to_string());
21429                    self.write("'");
21430                }
21431                self.write(": ");
21432                self.generate_expression(value)?;
21433            }
21434            self.write("}");
21435            return Ok(());
21436        }
21437
21438        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
21439            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
21440            self.write_keyword("OBJECT_CONSTRUCT");
21441            self.write("(");
21442            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21443                if i > 0 {
21444                    self.write(", ");
21445                }
21446                if let Some(n) = name {
21447                    self.write("'");
21448                    self.write(n);
21449                    self.write("'");
21450                } else {
21451                    self.write("'_");
21452                    self.write(&i.to_string());
21453                    self.write("'");
21454                }
21455                self.write(", ");
21456                self.generate_expression(value)?;
21457            }
21458            self.write(")");
21459            return Ok(());
21460        }
21461
21462        if matches!(
21463            self.config.dialect,
21464            Some(DialectType::Presto) | Some(DialectType::Trino)
21465        ) {
21466            if all_named && !names.is_empty() {
21467                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
21468                // Need to infer types from values
21469                self.write_keyword("CAST");
21470                self.write("(");
21471                self.write_keyword("ROW");
21472                self.write("(");
21473                for (i, value) in values.iter().enumerate() {
21474                    if i > 0 {
21475                        self.write(", ");
21476                    }
21477                    self.generate_expression(value)?;
21478                }
21479                self.write(")");
21480                self.write(" ");
21481                self.write_keyword("AS");
21482                self.write(" ");
21483                self.write_keyword("ROW");
21484                self.write("(");
21485                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21486                    if i > 0 {
21487                        self.write(", ");
21488                    }
21489                    if let Some(n) = name {
21490                        self.write(n);
21491                    }
21492                    self.write(" ");
21493                    let type_str = Self::infer_sql_type_for_presto(value);
21494                    self.write_keyword(&type_str);
21495                }
21496                self.write(")");
21497                self.write(")");
21498            } else {
21499                // Unnamed: ROW(values...)
21500                self.write_keyword("ROW");
21501                self.write("(");
21502                for (i, value) in values.iter().enumerate() {
21503                    if i > 0 {
21504                        self.write(", ");
21505                    }
21506                    self.generate_expression(value)?;
21507                }
21508                self.write(")");
21509            }
21510            return Ok(());
21511        }
21512
21513        // Default: ROW(values...) for other dialects
21514        self.write_keyword("ROW");
21515        self.write("(");
21516        for (i, value) in values.iter().enumerate() {
21517            if i > 0 {
21518                self.write(", ");
21519            }
21520            self.generate_expression(value)?;
21521        }
21522        self.write(")");
21523        Ok(())
21524    }
21525
21526    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
21527    fn infer_sql_type_for_presto(expr: &Expression) -> String {
21528        match expr {
21529            Expression::Literal(lit)
21530                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
21531            {
21532                "VARCHAR".to_string()
21533            }
21534            Expression::Literal(lit)
21535                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
21536            {
21537                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
21538                    unreachable!()
21539                };
21540                if n.contains('.') {
21541                    "DOUBLE".to_string()
21542                } else {
21543                    "INTEGER".to_string()
21544                }
21545            }
21546            Expression::Boolean(_) => "BOOLEAN".to_string(),
21547            Expression::Literal(lit)
21548                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
21549            {
21550                "DATE".to_string()
21551            }
21552            Expression::Literal(lit)
21553                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
21554            {
21555                "TIMESTAMP".to_string()
21556            }
21557            Expression::Literal(lit)
21558                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
21559            {
21560                "TIMESTAMP".to_string()
21561            }
21562            Expression::Array(_) | Expression::ArrayFunc(_) => {
21563                // Try to infer element type from first element
21564                "ARRAY(VARCHAR)".to_string()
21565            }
21566            // For nested structs - generate a nested ROW type by inspecting fields
21567            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
21568            Expression::Function(f) => {
21569                if f.name.eq_ignore_ascii_case("STRUCT") {
21570                    "ROW".to_string()
21571                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
21572                    "DATE".to_string()
21573                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
21574                    || f.name.eq_ignore_ascii_case("NOW")
21575                {
21576                    "TIMESTAMP".to_string()
21577                } else {
21578                    "VARCHAR".to_string()
21579                }
21580            }
21581            _ => "VARCHAR".to_string(),
21582        }
21583    }
21584
21585    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
21586        // DuckDB uses STRUCT_EXTRACT function syntax
21587        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
21588            self.write_keyword("STRUCT_EXTRACT");
21589            self.write("(");
21590            self.generate_expression(&f.this)?;
21591            self.write(", ");
21592            // Output field name as string literal
21593            self.write("'");
21594            self.write(&f.field.name);
21595            self.write("'");
21596            self.write(")");
21597            return Ok(());
21598        }
21599        self.generate_expression(&f.this)?;
21600        self.write(".");
21601        self.generate_identifier(&f.field)
21602    }
21603
21604    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
21605        if matches!(
21606            self.config.dialect,
21607            Some(DialectType::Spark | DialectType::Databricks)
21608        ) {
21609            self.write_keyword("STRUCT");
21610            self.write("(");
21611            for (i, (name, value)) in f.pairs.iter().enumerate() {
21612                if i > 0 {
21613                    self.write(", ");
21614                }
21615                self.generate_expression(value)?;
21616                self.write(" ");
21617                self.write_keyword("AS");
21618                self.write(" ");
21619                if let Expression::Literal(lit) = name {
21620                    if let Literal::String(field_name) = lit.as_ref() {
21621                        self.generate_identifier(&Identifier::new(field_name))?;
21622                    } else {
21623                        self.generate_expression(name)?;
21624                    }
21625                } else {
21626                    self.generate_expression(name)?;
21627                }
21628            }
21629            self.write(")");
21630            return Ok(());
21631        }
21632
21633        self.write_keyword("NAMED_STRUCT");
21634        self.write("(");
21635        for (i, (name, value)) in f.pairs.iter().enumerate() {
21636            if i > 0 {
21637                self.write(", ");
21638            }
21639            self.generate_expression(name)?;
21640            self.write(", ");
21641            self.generate_expression(value)?;
21642        }
21643        self.write(")");
21644        Ok(())
21645    }
21646
21647    // Map function generators
21648
21649    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
21650        if f.curly_brace_syntax {
21651            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
21652            if f.with_map_keyword {
21653                self.write_keyword("MAP");
21654                self.write(" ");
21655            }
21656            self.write("{");
21657            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
21658                if i > 0 {
21659                    self.write(", ");
21660                }
21661                self.generate_expression(key)?;
21662                self.write(": ");
21663                self.generate_expression(val)?;
21664            }
21665            self.write("}");
21666        } else {
21667            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
21668            self.write_keyword("MAP");
21669            self.write("(");
21670            self.write_keyword("ARRAY");
21671            self.write("[");
21672            for (i, key) in f.keys.iter().enumerate() {
21673                if i > 0 {
21674                    self.write(", ");
21675                }
21676                self.generate_expression(key)?;
21677            }
21678            self.write("], ");
21679            self.write_keyword("ARRAY");
21680            self.write("[");
21681            for (i, val) in f.values.iter().enumerate() {
21682                if i > 0 {
21683                    self.write(", ");
21684                }
21685                self.generate_expression(val)?;
21686            }
21687            self.write("])");
21688        }
21689        Ok(())
21690    }
21691
21692    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
21693        self.write_keyword(name);
21694        self.write("(");
21695        self.generate_expression(&f.this)?;
21696        self.write(", ");
21697        self.generate_expression(&f.transform)?;
21698        self.write(")");
21699        Ok(())
21700    }
21701
21702    // JSON function generators
21703
21704    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
21705        use crate::dialects::DialectType;
21706
21707        // Check if we should use arrow syntax (-> or ->>)
21708        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
21709
21710        if use_arrow {
21711            // Output arrow syntax: expr -> path or expr ->> path
21712            self.generate_expression(&f.this)?;
21713            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
21714                self.write(" ->> ");
21715            } else {
21716                self.write(" -> ");
21717            }
21718            self.generate_expression(&f.path)?;
21719            return Ok(());
21720        }
21721
21722        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
21723        if f.hash_arrow_syntax
21724            && matches!(
21725                self.config.dialect,
21726                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21727            )
21728        {
21729            self.generate_expression(&f.this)?;
21730            self.write(" #>> ");
21731            self.generate_expression(&f.path)?;
21732            return Ok(());
21733        }
21734
21735        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
21736        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
21737        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
21738            match name {
21739                "JSON_EXTRACT_SCALAR"
21740                | "JSON_EXTRACT_PATH_TEXT"
21741                | "JSON_EXTRACT"
21742                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
21743                _ => name,
21744            }
21745        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
21746            match name {
21747                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
21748                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
21749                _ => name,
21750            }
21751        } else {
21752            name
21753        };
21754
21755        self.write_keyword(func_name);
21756        self.write("(");
21757        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
21758        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
21759            if let Expression::Cast(ref cast) = f.this {
21760                if matches!(cast.to, crate::expressions::DataType::Json) {
21761                    self.generate_expression(&cast.this)?;
21762                } else {
21763                    self.generate_expression(&f.this)?;
21764                }
21765            } else {
21766                self.generate_expression(&f.this)?;
21767            }
21768        } else {
21769            self.generate_expression(&f.this)?;
21770        }
21771        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
21772        // decompose JSON path into separate string arguments
21773        if matches!(
21774            self.config.dialect,
21775            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21776        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
21777        {
21778            if let Expression::Literal(ref lit) = f.path {
21779                if let Literal::String(ref s) = lit.as_ref() {
21780                    let parts = Self::decompose_json_path(s);
21781                    for part in &parts {
21782                        self.write(", '");
21783                        self.write(part);
21784                        self.write("'");
21785                    }
21786                }
21787            } else {
21788                self.write(", ");
21789                self.generate_expression(&f.path)?;
21790            }
21791        } else {
21792            self.write(", ");
21793            self.generate_expression(&f.path)?;
21794        }
21795
21796        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
21797        // These go BEFORE the closing parenthesis
21798        if let Some(ref wrapper) = f.wrapper_option {
21799            self.write_space();
21800            self.write_keyword(wrapper);
21801        }
21802        if let Some(ref quotes) = f.quotes_option {
21803            self.write_space();
21804            self.write_keyword(quotes);
21805            if f.on_scalar_string {
21806                self.write_space();
21807                self.write_keyword("ON SCALAR STRING");
21808            }
21809        }
21810        if let Some(ref on_err) = f.on_error {
21811            self.write_space();
21812            self.write_keyword(on_err);
21813        }
21814        if let Some(ref ret_type) = f.returning {
21815            self.write_space();
21816            self.write_keyword("RETURNING");
21817            self.write_space();
21818            self.generate_data_type(ret_type)?;
21819        }
21820
21821        self.write(")");
21822        Ok(())
21823    }
21824
21825    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
21826    fn dialect_supports_json_arrow(&self) -> bool {
21827        use crate::dialects::DialectType;
21828        match self.config.dialect {
21829            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
21830            Some(DialectType::PostgreSQL) => true,
21831            Some(DialectType::MySQL) => true,
21832            Some(DialectType::DuckDB) => true,
21833            Some(DialectType::CockroachDB) => true,
21834            Some(DialectType::StarRocks) => true,
21835            Some(DialectType::SQLite) => true,
21836            // Other dialects use function syntax
21837            _ => false,
21838        }
21839    }
21840
21841    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
21842        use crate::dialects::DialectType;
21843
21844        // PostgreSQL uses #> operator for JSONB path extraction
21845        if matches!(
21846            self.config.dialect,
21847            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21848        ) && name == "JSON_EXTRACT_PATH"
21849        {
21850            self.generate_expression(&f.this)?;
21851            self.write(" #> ");
21852            if f.paths.len() == 1 {
21853                self.generate_expression(&f.paths[0])?;
21854            } else {
21855                // Multiple paths: ARRAY[path1, path2, ...]
21856                self.write_keyword("ARRAY");
21857                self.write("[");
21858                for (i, path) in f.paths.iter().enumerate() {
21859                    if i > 0 {
21860                        self.write(", ");
21861                    }
21862                    self.generate_expression(path)?;
21863                }
21864                self.write("]");
21865            }
21866            return Ok(());
21867        }
21868
21869        self.write_keyword(name);
21870        self.write("(");
21871        self.generate_expression(&f.this)?;
21872        for path in &f.paths {
21873            self.write(", ");
21874            self.generate_expression(path)?;
21875        }
21876        self.write(")");
21877        Ok(())
21878    }
21879
21880    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
21881        use crate::dialects::DialectType;
21882
21883        self.write_keyword("JSON_OBJECT");
21884        self.write("(");
21885        if f.star {
21886            self.write("*");
21887        } else {
21888            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
21889            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
21890            // Also respect the json_key_value_pair_sep config
21891            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
21892                || matches!(
21893                    self.config.dialect,
21894                    Some(DialectType::BigQuery)
21895                        | Some(DialectType::MySQL)
21896                        | Some(DialectType::SQLite)
21897                );
21898
21899            for (i, (key, value)) in f.pairs.iter().enumerate() {
21900                if i > 0 {
21901                    self.write(", ");
21902                }
21903                self.generate_expression(key)?;
21904                if use_comma_syntax {
21905                    self.write(", ");
21906                } else {
21907                    self.write(": ");
21908                }
21909                self.generate_expression(value)?;
21910            }
21911        }
21912        if let Some(null_handling) = f.null_handling {
21913            self.write_space();
21914            match null_handling {
21915                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21916                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21917            }
21918        }
21919        if f.with_unique_keys {
21920            self.write_space();
21921            self.write_keyword("WITH UNIQUE KEYS");
21922        }
21923        if let Some(ref ret_type) = f.returning_type {
21924            self.write_space();
21925            self.write_keyword("RETURNING");
21926            self.write_space();
21927            self.generate_data_type(ret_type)?;
21928            if f.format_json {
21929                self.write_space();
21930                self.write_keyword("FORMAT JSON");
21931            }
21932            if let Some(ref enc) = f.encoding {
21933                self.write_space();
21934                self.write_keyword("ENCODING");
21935                self.write_space();
21936                self.write(enc);
21937            }
21938        }
21939        self.write(")");
21940        Ok(())
21941    }
21942
21943    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
21944        self.write_keyword(name);
21945        self.write("(");
21946        self.generate_expression(&f.this)?;
21947        for (path, value) in &f.path_values {
21948            self.write(", ");
21949            self.generate_expression(path)?;
21950            self.write(", ");
21951            self.generate_expression(value)?;
21952        }
21953        self.write(")");
21954        Ok(())
21955    }
21956
21957    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
21958        self.write_keyword("JSON_ARRAYAGG");
21959        self.write("(");
21960        self.generate_expression(&f.this)?;
21961        if let Some(ref order_by) = f.order_by {
21962            self.write_space();
21963            self.write_keyword("ORDER BY");
21964            self.write_space();
21965            for (i, ord) in order_by.iter().enumerate() {
21966                if i > 0 {
21967                    self.write(", ");
21968                }
21969                self.generate_ordered(ord)?;
21970            }
21971        }
21972        if let Some(null_handling) = f.null_handling {
21973            self.write_space();
21974            match null_handling {
21975                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21976                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21977            }
21978        }
21979        self.write(")");
21980        if let Some(ref filter) = f.filter {
21981            self.write_space();
21982            self.write_keyword("FILTER");
21983            self.write("(");
21984            self.write_keyword("WHERE");
21985            self.write_space();
21986            self.generate_expression(filter)?;
21987            self.write(")");
21988        }
21989        Ok(())
21990    }
21991
21992    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
21993        self.write_keyword("JSON_OBJECTAGG");
21994        self.write("(");
21995        self.generate_expression(&f.key)?;
21996        self.write(": ");
21997        self.generate_expression(&f.value)?;
21998        if let Some(null_handling) = f.null_handling {
21999            self.write_space();
22000            match null_handling {
22001                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
22002                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
22003            }
22004        }
22005        self.write(")");
22006        if let Some(ref filter) = f.filter {
22007            self.write_space();
22008            self.write_keyword("FILTER");
22009            self.write("(");
22010            self.write_keyword("WHERE");
22011            self.write_space();
22012            self.generate_expression(filter)?;
22013            self.write(")");
22014        }
22015        Ok(())
22016    }
22017
22018    // Type casting/conversion generators
22019
22020    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
22021        use crate::dialects::DialectType;
22022
22023        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
22024        if self.config.dialect == Some(DialectType::Redshift) {
22025            self.write_keyword("CAST");
22026            self.write("(");
22027            self.generate_expression(&f.this)?;
22028            self.write_space();
22029            self.write_keyword("AS");
22030            self.write_space();
22031            self.generate_data_type(&f.to)?;
22032            self.write(")");
22033            return Ok(());
22034        }
22035
22036        self.write_keyword("CONVERT");
22037        self.write("(");
22038        self.generate_data_type(&f.to)?;
22039        self.write(", ");
22040        self.generate_expression(&f.this)?;
22041        if let Some(ref style) = f.style {
22042            self.write(", ");
22043            self.generate_expression(style)?;
22044        }
22045        self.write(")");
22046        Ok(())
22047    }
22048
22049    // Additional expression generators
22050
22051    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
22052        if f.colon {
22053            // DuckDB syntax: LAMBDA x : expr
22054            self.write_keyword("LAMBDA");
22055            self.write_space();
22056            for (i, param) in f.parameters.iter().enumerate() {
22057                if i > 0 {
22058                    self.write(", ");
22059                }
22060                self.generate_identifier(param)?;
22061            }
22062            self.write(" : ");
22063        } else {
22064            // Standard syntax: x -> expr or (x, y) -> expr
22065            if f.parameters.len() == 1 {
22066                self.generate_identifier(&f.parameters[0])?;
22067            } else {
22068                self.write("(");
22069                for (i, param) in f.parameters.iter().enumerate() {
22070                    if i > 0 {
22071                        self.write(", ");
22072                    }
22073                    self.generate_identifier(param)?;
22074                }
22075                self.write(")");
22076            }
22077            self.write(" -> ");
22078        }
22079        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22080            if let Expression::Lambda(inner) = &f.body {
22081                self.generate_lambda_with_parenthesized_single_param(inner)?;
22082                return Ok(());
22083            }
22084        }
22085
22086        self.generate_expression(&f.body)
22087    }
22088
22089    fn generate_lambda_with_parenthesized_single_param(&mut self, f: &LambdaExpr) -> Result<()> {
22090        if f.colon {
22091            return self.generate_lambda(f);
22092        }
22093
22094        self.write("(");
22095        for (i, param) in f.parameters.iter().enumerate() {
22096            if i > 0 {
22097                self.write(", ");
22098            }
22099            self.generate_identifier(param)?;
22100        }
22101        self.write(") -> ");
22102        self.generate_expression(&f.body)
22103    }
22104
22105    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
22106        self.generate_identifier(&f.name)?;
22107        match f.separator {
22108            NamedArgSeparator::DArrow => self.write(" => "),
22109            NamedArgSeparator::ColonEq => self.write(" := "),
22110            NamedArgSeparator::Eq => self.write(" = "),
22111        }
22112        self.generate_expression(&f.value)
22113    }
22114
22115    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
22116        self.write_keyword(&f.prefix);
22117        self.write(" ");
22118        self.generate_expression(&f.this)
22119    }
22120
22121    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
22122        match f.style {
22123            ParameterStyle::Question => self.write("?"),
22124            ParameterStyle::Dollar => {
22125                self.write("$");
22126                if let Some(idx) = f.index {
22127                    self.write(&idx.to_string());
22128                } else if let Some(ref name) = f.name {
22129                    // Session variable like $x or $query_id
22130                    self.write(name);
22131                }
22132            }
22133            ParameterStyle::DollarBrace => {
22134                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
22135                self.write("${");
22136                if let Some(ref name) = f.name {
22137                    self.write(name);
22138                }
22139                if let Some(ref expr) = f.expression {
22140                    self.write(":");
22141                    self.write(expr);
22142                }
22143                self.write("}");
22144            }
22145            ParameterStyle::Colon => {
22146                self.write(":");
22147                if let Some(idx) = f.index {
22148                    self.write(&idx.to_string());
22149                } else if let Some(ref name) = f.name {
22150                    self.write(name);
22151                }
22152            }
22153            ParameterStyle::At => {
22154                self.write("@");
22155                if let Some(ref name) = f.name {
22156                    if f.string_quoted {
22157                        self.write("'");
22158                        self.write(name);
22159                        self.write("'");
22160                    } else if f.quoted {
22161                        self.write("\"");
22162                        self.write(name);
22163                        self.write("\"");
22164                    } else {
22165                        self.write(name);
22166                    }
22167                }
22168            }
22169            ParameterStyle::DoubleAt => {
22170                self.write("@@");
22171                if let Some(ref name) = f.name {
22172                    self.write(name);
22173                }
22174            }
22175            ParameterStyle::DoubleDollar => {
22176                self.write("$$");
22177                if let Some(ref name) = f.name {
22178                    self.write(name);
22179                }
22180            }
22181            ParameterStyle::Percent => {
22182                if let Some(ref name) = f.name {
22183                    // %(name)s format
22184                    self.write("%(");
22185                    self.write(name);
22186                    self.write(")s");
22187                } else {
22188                    // %s format
22189                    self.write("%s");
22190                }
22191            }
22192            ParameterStyle::Brace => {
22193                // Spark/Databricks widget template variable: {name}
22194                // ClickHouse query parameter may include kind: {name: Type}
22195                self.write("{");
22196                if let Some(ref name) = f.name {
22197                    self.write(name);
22198                }
22199                if let Some(ref expr) = f.expression {
22200                    self.write(": ");
22201                    self.write(expr);
22202                }
22203                self.write("}");
22204            }
22205        }
22206        Ok(())
22207    }
22208
22209    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
22210        self.write("?");
22211        if let Some(idx) = f.index {
22212            self.write(&idx.to_string());
22213        }
22214        Ok(())
22215    }
22216
22217    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
22218        if f.is_block {
22219            self.write("/*");
22220            self.write(&f.text);
22221            self.write("*/");
22222        } else {
22223            self.write("--");
22224            self.write(&f.text);
22225        }
22226        Ok(())
22227    }
22228
22229    // Additional predicate generators
22230
22231    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
22232        self.generate_expression(&f.this)?;
22233        if f.not {
22234            self.write_space();
22235            self.write_keyword("NOT");
22236        }
22237        self.write_space();
22238        self.write_keyword("SIMILAR TO");
22239        self.write_space();
22240        self.generate_expression(&f.pattern)?;
22241        if let Some(ref escape) = f.escape {
22242            self.write_space();
22243            self.write_keyword("ESCAPE");
22244            self.write_space();
22245            self.generate_expression(escape)?;
22246        }
22247        Ok(())
22248    }
22249
22250    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
22251        self.generate_expression(&f.this)?;
22252        self.write_space();
22253        // Output comparison operator if present
22254        if let Some(op) = &f.op {
22255            match op {
22256                QuantifiedOp::Eq => self.write("="),
22257                QuantifiedOp::Neq => self.write("<>"),
22258                QuantifiedOp::Lt => self.write("<"),
22259                QuantifiedOp::Lte => self.write("<="),
22260                QuantifiedOp::Gt => self.write(">"),
22261                QuantifiedOp::Gte => self.write(">="),
22262            }
22263            self.write_space();
22264        }
22265        self.write_keyword(name);
22266
22267        // If the child is a Subquery, it provides its own parens — output with space
22268        if matches!(&f.subquery, Expression::Subquery(_)) {
22269            self.write_space();
22270            self.generate_expression(&f.subquery)?;
22271        } else {
22272            let is_statement = matches!(
22273                &f.subquery,
22274                Expression::Select(_)
22275                    | Expression::Union(_)
22276                    | Expression::Intersect(_)
22277                    | Expression::Except(_)
22278            );
22279            if is_statement
22280                && !self.config.quantified_no_paren_space
22281                && matches!(self.config.dialect, Some(DialectType::ClickHouse))
22282            {
22283                self.write_space();
22284            }
22285            self.write("(");
22286
22287            if self.config.pretty && is_statement {
22288                self.write_newline();
22289                self.indent_level += 1;
22290                self.write_indent();
22291            }
22292            self.generate_expression(&f.subquery)?;
22293            if self.config.pretty && is_statement {
22294                self.write_newline();
22295                self.indent_level -= 1;
22296                self.write_indent();
22297            }
22298            self.write(")");
22299        }
22300        Ok(())
22301    }
22302
22303    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
22304        // Check if this is a simple binary form (this OVERLAPS expression)
22305        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
22306            self.generate_expression(this)?;
22307            self.write_space();
22308            self.write_keyword("OVERLAPS");
22309            self.write_space();
22310            self.generate_expression(expr)?;
22311        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
22312            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
22313        {
22314            // Full ANSI form: (a, b) OVERLAPS (c, d)
22315            self.write("(");
22316            self.generate_expression(ls)?;
22317            self.write(", ");
22318            self.generate_expression(le)?;
22319            self.write(")");
22320            self.write_space();
22321            self.write_keyword("OVERLAPS");
22322            self.write_space();
22323            self.write("(");
22324            self.generate_expression(rs)?;
22325            self.write(", ");
22326            self.generate_expression(re)?;
22327            self.write(")");
22328        }
22329        Ok(())
22330    }
22331
22332    // Type conversion generators
22333
22334    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
22335        use crate::dialects::DialectType;
22336
22337        // SingleStore uses !:> syntax for try cast
22338        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
22339            self.generate_expression(&cast.this)?;
22340            self.write(" !:> ");
22341            self.generate_data_type(&cast.to)?;
22342            return Ok(());
22343        }
22344
22345        // Teradata uses TRYCAST (no underscore)
22346        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
22347            self.write_keyword("TRYCAST");
22348            self.write("(");
22349            self.generate_expression(&cast.this)?;
22350            self.write_space();
22351            self.write_keyword("AS");
22352            self.write_space();
22353            self.generate_data_type(&cast.to)?;
22354            self.write(")");
22355            return Ok(());
22356        }
22357
22358        // Dialects without TRY_CAST: generate as regular CAST
22359        let keyword = if matches!(
22360            self.config.dialect,
22361            Some(DialectType::Hive)
22362                | Some(DialectType::MySQL)
22363                | Some(DialectType::SQLite)
22364                | Some(DialectType::Oracle)
22365                | Some(DialectType::ClickHouse)
22366                | Some(DialectType::Redshift)
22367                | Some(DialectType::PostgreSQL)
22368                | Some(DialectType::StarRocks)
22369                | Some(DialectType::Doris)
22370        ) {
22371            "CAST"
22372        } else {
22373            "TRY_CAST"
22374        };
22375
22376        self.write_keyword(keyword);
22377        self.write("(");
22378        self.generate_expression(&cast.this)?;
22379        self.write_space();
22380        self.write_keyword("AS");
22381        self.write_space();
22382        self.generate_data_type(&cast.to)?;
22383
22384        // Output FORMAT clause if present
22385        if let Some(format) = &cast.format {
22386            self.write_space();
22387            self.write_keyword("FORMAT");
22388            self.write_space();
22389            self.generate_expression(format)?;
22390        }
22391
22392        self.write(")");
22393        Ok(())
22394    }
22395
22396    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
22397        self.write_keyword("SAFE_CAST");
22398        self.write("(");
22399        self.generate_expression(&cast.this)?;
22400        self.write_space();
22401        self.write_keyword("AS");
22402        self.write_space();
22403        self.generate_data_type(&cast.to)?;
22404
22405        // Output FORMAT clause if present
22406        if let Some(format) = &cast.format {
22407            self.write_space();
22408            self.write_keyword("FORMAT");
22409            self.write_space();
22410            self.generate_expression(format)?;
22411        }
22412
22413        self.write(")");
22414        Ok(())
22415    }
22416
22417    // Array/struct/map access generators
22418
22419    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
22420        // Wrap the base expression in parentheses when it uses arrow syntax (->)
22421        // which has lower precedence than bracket subscript ([]).
22422        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
22423        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
22424        if needs_parens {
22425            self.write("(");
22426        }
22427        self.generate_expression(&s.this)?;
22428        if needs_parens {
22429            self.write(")");
22430        }
22431        self.write("[");
22432        self.generate_expression(&s.index)?;
22433        self.write("]");
22434        Ok(())
22435    }
22436
22437    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
22438        self.generate_expression(&d.this)?;
22439        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
22440        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
22441        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
22442            && matches!(
22443                &d.this,
22444                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
22445            );
22446        if use_colon {
22447            self.write(":");
22448        } else {
22449            self.write(".");
22450        }
22451        self.generate_identifier(&d.field)
22452    }
22453
22454    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
22455        self.generate_expression(&m.this)?;
22456        self.write(".");
22457        // Method names after a dot should not be quoted based on reserved keywords
22458        // Only quote if explicitly marked as quoted in the AST
22459        if m.method.quoted {
22460            let q = self.config.identifier_quote;
22461            self.write(&format!("{}{}{}", q, m.method.name, q));
22462        } else {
22463            self.write(&m.method.name);
22464        }
22465        self.write("(");
22466        for (i, arg) in m.args.iter().enumerate() {
22467            if i > 0 {
22468                self.write(", ");
22469            }
22470            self.generate_expression(arg)?;
22471        }
22472        self.write(")");
22473        Ok(())
22474    }
22475
22476    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
22477        // Check if we need to wrap the inner expression in parentheses
22478        // JSON arrow expressions have lower precedence than array subscript
22479        let needs_parens = matches!(
22480            &s.this,
22481            Expression::JsonExtract(f) if f.arrow_syntax
22482        ) || matches!(
22483            &s.this,
22484            Expression::JsonExtractScalar(f) if f.arrow_syntax
22485        );
22486
22487        if needs_parens {
22488            self.write("(");
22489        }
22490        self.generate_expression(&s.this)?;
22491        if needs_parens {
22492            self.write(")");
22493        }
22494        self.write("[");
22495        if let Some(start) = &s.start {
22496            self.generate_expression(start)?;
22497        }
22498        self.write(":");
22499        if let Some(end) = &s.end {
22500            self.generate_expression(end)?;
22501        }
22502        self.write("]");
22503        Ok(())
22504    }
22505
22506    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22507        // Generate left expression, but skip trailing comments if they're already in left_comments
22508        // to avoid duplication (comments are captured as both expr.trailing_comments
22509        // and BinaryOp.left_comments during parsing)
22510        match &op.left {
22511            Expression::Column(col) => {
22512                // Generate column with trailing comments but skip them if they're
22513                // already captured in BinaryOp.left_comments to avoid duplication
22514                if let Some(table) = &col.table {
22515                    self.generate_identifier(table)?;
22516                    self.write(".");
22517                }
22518                self.generate_identifier(&col.name)?;
22519                // Oracle-style join marker (+)
22520                if col.join_mark && self.config.supports_column_join_marks {
22521                    self.write(" (+)");
22522                }
22523                // Output column trailing comments if they're not already in left_comments
22524                if op.left_comments.is_empty() {
22525                    for comment in &col.trailing_comments {
22526                        self.write_space();
22527                        self.write_formatted_comment(comment);
22528                    }
22529                }
22530            }
22531            Expression::Add(inner_op)
22532            | Expression::Sub(inner_op)
22533            | Expression::Mul(inner_op)
22534            | Expression::Div(inner_op)
22535            | Expression::Concat(inner_op) => {
22536                // Generate binary op without its trailing comments
22537                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22538                    Expression::Add(_) => "+",
22539                    Expression::Sub(_) => "-",
22540                    Expression::Mul(_) => "*",
22541                    Expression::Div(_) => "/",
22542                    Expression::Concat(_) => "||",
22543                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22544                })?;
22545            }
22546            _ => {
22547                self.generate_expression(&op.left)?;
22548            }
22549        }
22550        // Output comments after left operand
22551        for comment in &op.left_comments {
22552            self.write_space();
22553            self.write_formatted_comment(comment);
22554        }
22555        if self.config.pretty
22556            && matches!(self.config.dialect, Some(DialectType::Snowflake))
22557            && (operator == "AND" || operator == "OR")
22558        {
22559            self.write_newline();
22560            self.write_indent();
22561            self.write_keyword(operator);
22562        } else {
22563            self.write_space();
22564            if operator.chars().all(|c| c.is_alphabetic()) {
22565                self.write_keyword(operator);
22566            } else {
22567                self.write(operator);
22568            }
22569        }
22570        // Output comments after operator (before right operand)
22571        for comment in &op.operator_comments {
22572            self.write_space();
22573            self.write_formatted_comment(comment);
22574        }
22575        self.write_space();
22576        self.generate_expression(&op.right)?;
22577        // Output trailing comments after right operand
22578        for comment in &op.trailing_comments {
22579            self.write_space();
22580            self.write_formatted_comment(comment);
22581        }
22582        Ok(())
22583    }
22584
22585    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
22586        let keyword = connector.keyword();
22587        let Some(terms) = self.flatten_connector_terms(op, connector) else {
22588            return self.generate_binary_op(op, keyword);
22589        };
22590
22591        let wrap_clickhouse_or_term = |generator: &mut Self, term: &Expression| -> Result<()> {
22592            let should_wrap = matches!(connector, ConnectorOperator::Or)
22593                && matches!(generator.config.dialect, Some(DialectType::ClickHouse))
22594                && matches!(
22595                    generator.config.source_dialect,
22596                    Some(DialectType::ClickHouse)
22597                )
22598                && matches!(term, Expression::And(_));
22599            if should_wrap {
22600                generator.write("(");
22601                generator.generate_expression(term)?;
22602                generator.write(")");
22603            } else {
22604                generator.generate_expression(term)?;
22605            }
22606            Ok(())
22607        };
22608
22609        wrap_clickhouse_or_term(self, terms[0])?;
22610        for term in terms.iter().skip(1) {
22611            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
22612                self.write_newline();
22613                self.write_indent();
22614                self.write_keyword(keyword);
22615            } else {
22616                self.write_space();
22617                self.write_keyword(keyword);
22618            }
22619            self.write_space();
22620            wrap_clickhouse_or_term(self, term)?;
22621        }
22622
22623        Ok(())
22624    }
22625
22626    fn flatten_connector_terms<'a>(
22627        &self,
22628        root: &'a BinaryOp,
22629        connector: ConnectorOperator,
22630    ) -> Option<Vec<&'a Expression>> {
22631        if !root.left_comments.is_empty()
22632            || !root.operator_comments.is_empty()
22633            || !root.trailing_comments.is_empty()
22634        {
22635            return None;
22636        }
22637
22638        let mut terms = Vec::new();
22639        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
22640
22641        while let Some(expr) = stack.pop() {
22642            match (connector, expr) {
22643                (ConnectorOperator::And, Expression::And(inner))
22644                    if inner.left_comments.is_empty()
22645                        && inner.operator_comments.is_empty()
22646                        && inner.trailing_comments.is_empty() =>
22647                {
22648                    stack.push(&inner.right);
22649                    stack.push(&inner.left);
22650                }
22651                (ConnectorOperator::Or, Expression::Or(inner))
22652                    if inner.left_comments.is_empty()
22653                        && inner.operator_comments.is_empty()
22654                        && inner.trailing_comments.is_empty() =>
22655                {
22656                    stack.push(&inner.right);
22657                    stack.push(&inner.left);
22658                }
22659                _ => terms.push(expr),
22660            }
22661        }
22662
22663        if terms.len() > 1 {
22664            Some(terms)
22665        } else {
22666            None
22667        }
22668    }
22669
22670    fn missing_closing_parens_outside_quotes(sql: &str) -> usize {
22671        let mut depth = 0usize;
22672        let mut quote: Option<char> = None;
22673        let mut chars = sql.chars().peekable();
22674
22675        while let Some(ch) = chars.next() {
22676            if let Some(quote_char) = quote {
22677                if ch == '\\' {
22678                    chars.next();
22679                } else if ch == quote_char {
22680                    if quote_char == '\'' && chars.peek() == Some(&'\'') {
22681                        chars.next();
22682                    } else {
22683                        quote = None;
22684                    }
22685                }
22686                continue;
22687            }
22688
22689            match ch {
22690                '\'' | '"' | '`' => quote = Some(ch),
22691                '(' => depth += 1,
22692                ')' => depth = depth.saturating_sub(1),
22693                _ => {}
22694            }
22695        }
22696
22697        depth
22698    }
22699
22700    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
22701    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
22702        self.generate_like_op_inner(op, operator, false)
22703    }
22704
22705    fn generate_like_op_negated(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
22706        self.generate_like_op_inner(op, operator, true)
22707    }
22708
22709    fn generate_like_op_inner(&mut self, op: &LikeOp, operator: &str, negated: bool) -> Result<()> {
22710        if negated
22711            && matches!(
22712                self.config.dialect,
22713                Some(DialectType::ClickHouse)
22714                    | Some(DialectType::DataFusion)
22715                    | Some(DialectType::TSQL)
22716                    | Some(DialectType::Fabric)
22717            )
22718        {
22719            self.write_keyword("NOT");
22720            self.write_space();
22721            return self.generate_like_op_inner(op, operator, false);
22722        }
22723
22724        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22725            if let Expression::Star(star) = &op.left {
22726                if star
22727                    .except
22728                    .as_ref()
22729                    .is_some_and(|except| !except.is_empty())
22730                {
22731                    if let Some(table) = &star.table {
22732                        self.generate_identifier(table)?;
22733                        self.write(".");
22734                    }
22735                    self.write("*");
22736                    self.write_space();
22737                    self.write_keyword(operator);
22738                    if let Some(quantifier) = &op.quantifier {
22739                        self.write_space();
22740                        self.write_keyword(quantifier);
22741                        self.write_space();
22742                    } else {
22743                        self.write_space();
22744                    }
22745                    self.generate_expression(&op.right)?;
22746                    if let Some(escape) = &op.escape {
22747                        self.write_space();
22748                        self.write_keyword("ESCAPE");
22749                        self.write_space();
22750                        self.generate_expression(escape)?;
22751                    }
22752                    if let Some(except) = &star.except {
22753                        self.write_space();
22754                        self.write_keyword("EXCEPT");
22755                        self.write(" (");
22756                        for (i, col) in except.iter().enumerate() {
22757                            if i > 0 {
22758                                self.write(", ");
22759                            }
22760                            self.generate_identifier(col)?;
22761                        }
22762                        self.write(")");
22763                    }
22764                    return Ok(());
22765                }
22766            }
22767        }
22768
22769        self.generate_expression(&op.left)?;
22770        self.write_space();
22771        if negated {
22772            self.write_keyword("NOT");
22773            self.write_space();
22774        }
22775        // Drill backtick-quotes ILIKE
22776        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
22777            self.write("`ILIKE`");
22778        } else {
22779            self.write_keyword(operator);
22780        }
22781        if let Some(quantifier) = &op.quantifier {
22782            self.write_space();
22783            self.write_keyword(quantifier);
22784            // Match Python sqlglot behavior:
22785            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
22786            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
22787            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
22788            let is_any =
22789                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
22790            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
22791                self.write_space();
22792            }
22793        } else {
22794            self.write_space();
22795        }
22796        self.generate_expression(&op.right)?;
22797        if let Some(escape) = &op.escape {
22798            self.write_space();
22799            self.write_keyword("ESCAPE");
22800            self.write_space();
22801            self.generate_expression(escape)?;
22802        }
22803        Ok(())
22804    }
22805
22806    /// Generate null-safe equality
22807    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
22808    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
22809        use crate::dialects::DialectType;
22810        self.generate_expression(&op.left)?;
22811        self.write_space();
22812        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
22813            self.write("<=>");
22814        } else {
22815            self.write_keyword("IS NOT DISTINCT FROM");
22816        }
22817        self.write_space();
22818        self.generate_expression(&op.right)?;
22819        Ok(())
22820    }
22821
22822    /// Generate IS DISTINCT FROM (null-safe inequality)
22823    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
22824        self.generate_expression(&op.left)?;
22825        self.write_space();
22826        self.write_keyword("IS DISTINCT FROM");
22827        self.write_space();
22828        self.generate_expression(&op.right)?;
22829        Ok(())
22830    }
22831
22832    /// Generate binary op without trailing comments (used when nested inside another binary op)
22833    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22834        // Generate left expression, but skip trailing comments
22835        match &op.left {
22836            Expression::Column(col) => {
22837                if let Some(table) = &col.table {
22838                    self.generate_identifier(table)?;
22839                    self.write(".");
22840                }
22841                self.generate_identifier(&col.name)?;
22842                // Oracle-style join marker (+)
22843                if col.join_mark && self.config.supports_column_join_marks {
22844                    self.write(" (+)");
22845                }
22846            }
22847            Expression::Add(inner_op)
22848            | Expression::Sub(inner_op)
22849            | Expression::Mul(inner_op)
22850            | Expression::Div(inner_op)
22851            | Expression::Concat(inner_op) => {
22852                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22853                    Expression::Add(_) => "+",
22854                    Expression::Sub(_) => "-",
22855                    Expression::Mul(_) => "*",
22856                    Expression::Div(_) => "/",
22857                    Expression::Concat(_) => "||",
22858                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22859                })?;
22860            }
22861            _ => {
22862                self.generate_expression(&op.left)?;
22863            }
22864        }
22865        // Output left_comments
22866        for comment in &op.left_comments {
22867            self.write_space();
22868            self.write_formatted_comment(comment);
22869        }
22870        self.write_space();
22871        if operator.chars().all(|c| c.is_alphabetic()) {
22872            self.write_keyword(operator);
22873        } else {
22874            self.write(operator);
22875        }
22876        // Output operator_comments
22877        for comment in &op.operator_comments {
22878            self.write_space();
22879            self.write_formatted_comment(comment);
22880        }
22881        self.write_space();
22882        // Generate right expression, but skip trailing comments if it's a Column
22883        // (the parent's left_comments will output them)
22884        match &op.right {
22885            Expression::Column(col) => {
22886                if let Some(table) = &col.table {
22887                    self.generate_identifier(table)?;
22888                    self.write(".");
22889                }
22890                self.generate_identifier(&col.name)?;
22891                // Oracle-style join marker (+)
22892                if col.join_mark && self.config.supports_column_join_marks {
22893                    self.write(" (+)");
22894                }
22895            }
22896            _ => {
22897                self.generate_expression(&op.right)?;
22898            }
22899        }
22900        // Skip trailing_comments - parent will handle them via its left_comments
22901        Ok(())
22902    }
22903
22904    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
22905        if operator.chars().all(|c| c.is_alphabetic()) {
22906            self.write_keyword(operator);
22907            self.write_space();
22908        } else {
22909            self.write(operator);
22910            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
22911            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
22912                self.write_space();
22913            }
22914        }
22915        self.generate_expression(&op.this)
22916    }
22917
22918    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
22919        // Generic mode supports two styles for negated IN:
22920        // - Prefix: NOT a IN (...)
22921        // - Infix:  a NOT IN (...)
22922        let is_generic =
22923            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22924        let use_prefix_not =
22925            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
22926        if use_prefix_not {
22927            self.write_keyword("NOT");
22928            self.write_space();
22929        }
22930        self.generate_expression(&in_expr.this)?;
22931        if in_expr.global {
22932            self.write_space();
22933            self.write_keyword("GLOBAL");
22934        }
22935        if in_expr.not && !use_prefix_not {
22936            self.write_space();
22937            self.write_keyword("NOT");
22938        }
22939        self.write_space();
22940        self.write_keyword("IN");
22941
22942        // BigQuery: IN UNNEST(expr)
22943        if let Some(unnest_expr) = &in_expr.unnest {
22944            self.write_space();
22945            self.write_keyword("UNNEST");
22946            self.write("(");
22947            self.generate_expression(unnest_expr)?;
22948            self.write(")");
22949            return Ok(());
22950        }
22951
22952        if let Some(query) = &in_expr.query {
22953            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
22954            // vs a subquery (col IN (SELECT ...))
22955            let is_bare = in_expr.expressions.is_empty()
22956                && !matches!(
22957                    query,
22958                    Expression::Select(_)
22959                        | Expression::Union(_)
22960                        | Expression::Intersect(_)
22961                        | Expression::Except(_)
22962                        | Expression::Subquery(_)
22963                );
22964            if is_bare {
22965                // Bare identifier: no parentheses
22966                self.write_space();
22967                self.generate_expression(query)?;
22968            } else {
22969                // Subquery: with parentheses
22970                self.write(" (");
22971                let is_statement = matches!(
22972                    query,
22973                    Expression::Select(_)
22974                        | Expression::Union(_)
22975                        | Expression::Intersect(_)
22976                        | Expression::Except(_)
22977                        | Expression::Subquery(_)
22978                );
22979                if self.config.pretty && is_statement {
22980                    self.write_newline();
22981                    self.indent_level += 1;
22982                    self.write_indent();
22983                }
22984                self.generate_expression(query)?;
22985                if self.config.pretty && is_statement {
22986                    self.write_newline();
22987                    self.indent_level -= 1;
22988                    self.write_indent();
22989                }
22990                self.write(")");
22991            }
22992        } else {
22993            // DuckDB: IN without parentheses for single expression that is NOT a literal
22994            // (array/list membership like 'red' IN tbl.flags)
22995            // ClickHouse: IN without parentheses for single non-array expressions
22996            let is_duckdb = matches!(
22997                self.config.dialect,
22998                Some(crate::dialects::DialectType::DuckDB)
22999            );
23000            let is_clickhouse = matches!(
23001                self.config.dialect,
23002                Some(crate::dialects::DialectType::ClickHouse)
23003            );
23004            let single_expr = in_expr.expressions.len() == 1;
23005            if is_clickhouse && single_expr {
23006                if let Expression::Array(arr) = &in_expr.expressions[0] {
23007                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
23008                    self.write(" (");
23009                    for (i, expr) in arr.expressions.iter().enumerate() {
23010                        if i > 0 {
23011                            self.write(", ");
23012                        }
23013                        self.generate_expression(expr)?;
23014                    }
23015                    self.write(")");
23016                } else if in_expr.is_field {
23017                    self.write_space();
23018                    self.generate_expression(&in_expr.expressions[0])?;
23019                } else {
23020                    self.write(" (");
23021                    self.generate_expression(&in_expr.expressions[0])?;
23022                    self.write(")");
23023                }
23024            } else {
23025                let is_bare_ref = single_expr
23026                    && matches!(
23027                        &in_expr.expressions[0],
23028                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
23029                    );
23030                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
23031                    // Bare field reference (no parens in source): IN identifier
23032                    // Also DuckDB: IN without parentheses for array/list membership
23033                    self.write_space();
23034                    self.generate_expression(&in_expr.expressions[0])?;
23035                } else {
23036                    // Standard IN (list)
23037                    self.write(" (");
23038                    for (i, expr) in in_expr.expressions.iter().enumerate() {
23039                        if i > 0 {
23040                            self.write(", ");
23041                        }
23042                        self.generate_expression(expr)?;
23043                    }
23044                    self.write(")");
23045                }
23046            }
23047        }
23048
23049        Ok(())
23050    }
23051
23052    fn generate_between(&mut self, between: &Between) -> Result<()> {
23053        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
23054        let use_prefix_not = between.not
23055            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
23056        if use_prefix_not {
23057            self.write_keyword("NOT");
23058            self.write_space();
23059        }
23060        self.generate_expression(&between.this)?;
23061        if between.not && !use_prefix_not {
23062            self.write_space();
23063            self.write_keyword("NOT");
23064        }
23065        self.write_space();
23066        self.write_keyword("BETWEEN");
23067        // Emit SYMMETRIC/ASYMMETRIC if present
23068        if let Some(sym) = between.symmetric {
23069            if sym {
23070                self.write(" SYMMETRIC");
23071            } else {
23072                self.write(" ASYMMETRIC");
23073            }
23074        }
23075        self.write_space();
23076        self.generate_expression(&between.low)?;
23077        self.write_space();
23078        self.write_keyword("AND");
23079        self.write_space();
23080        self.generate_expression(&between.high)
23081    }
23082
23083    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
23084        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
23085        let use_prefix_not = is_null.not
23086            && (self.config.dialect.is_none()
23087                || self.config.dialect == Some(DialectType::Generic)
23088                || is_null.postfix_form);
23089        if use_prefix_not {
23090            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
23091            self.write_keyword("NOT");
23092            self.write_space();
23093            self.generate_expression(&is_null.this)?;
23094            self.write_space();
23095            self.write_keyword("IS");
23096            self.write_space();
23097            self.write_keyword("NULL");
23098        } else {
23099            self.generate_expression(&is_null.this)?;
23100            self.write_space();
23101            self.write_keyword("IS");
23102            if is_null.not {
23103                self.write_space();
23104                self.write_keyword("NOT");
23105            }
23106            self.write_space();
23107            self.write_keyword("NULL");
23108        }
23109        Ok(())
23110    }
23111
23112    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
23113        self.generate_expression(&is_true.this)?;
23114        self.write_space();
23115        self.write_keyword("IS");
23116        if is_true.not {
23117            self.write_space();
23118            self.write_keyword("NOT");
23119        }
23120        self.write_space();
23121        self.write_keyword("TRUE");
23122        Ok(())
23123    }
23124
23125    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
23126        self.generate_expression(&is_false.this)?;
23127        self.write_space();
23128        self.write_keyword("IS");
23129        if is_false.not {
23130            self.write_space();
23131            self.write_keyword("NOT");
23132        }
23133        self.write_space();
23134        self.write_keyword("FALSE");
23135        Ok(())
23136    }
23137
23138    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
23139        self.generate_expression(&is_json.this)?;
23140        self.write_space();
23141        self.write_keyword("IS");
23142        if is_json.negated {
23143            self.write_space();
23144            self.write_keyword("NOT");
23145        }
23146        self.write_space();
23147        self.write_keyword("JSON");
23148
23149        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
23150        if let Some(ref json_type) = is_json.json_type {
23151            self.write_space();
23152            self.write_keyword(json_type);
23153        }
23154
23155        // Output key uniqueness constraint if specified
23156        match &is_json.unique_keys {
23157            Some(JsonUniqueKeys::With) => {
23158                self.write_space();
23159                self.write_keyword("WITH UNIQUE KEYS");
23160            }
23161            Some(JsonUniqueKeys::Without) => {
23162                self.write_space();
23163                self.write_keyword("WITHOUT UNIQUE KEYS");
23164            }
23165            Some(JsonUniqueKeys::Shorthand) => {
23166                self.write_space();
23167                self.write_keyword("UNIQUE KEYS");
23168            }
23169            None => {}
23170        }
23171
23172        Ok(())
23173    }
23174
23175    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
23176        self.generate_expression(&is_expr.left)?;
23177        self.write_space();
23178        self.write_keyword("IS");
23179        self.write_space();
23180        self.generate_expression(&is_expr.right)
23181    }
23182
23183    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
23184        if exists.not {
23185            self.write_keyword("NOT");
23186            self.write_space();
23187        }
23188        self.write_keyword("EXISTS");
23189        self.write("(");
23190        let is_statement = matches!(
23191            &exists.this,
23192            Expression::Select(_)
23193                | Expression::Union(_)
23194                | Expression::Intersect(_)
23195                | Expression::Except(_)
23196        );
23197        if self.config.pretty && is_statement {
23198            self.write_newline();
23199            self.indent_level += 1;
23200            self.write_indent();
23201            self.generate_expression(&exists.this)?;
23202            self.write_newline();
23203            self.indent_level -= 1;
23204            self.write_indent();
23205            self.write(")");
23206        } else {
23207            self.generate_expression(&exists.this)?;
23208            self.write(")");
23209        }
23210        Ok(())
23211    }
23212
23213    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
23214        self.generate_expression(&op.left)?;
23215        self.write_space();
23216        self.write_keyword("MEMBER OF");
23217        self.write("(");
23218        self.generate_expression(&op.right)?;
23219        self.write(")");
23220        Ok(())
23221    }
23222
23223    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
23224        if subquery.lateral {
23225            self.write_keyword("LATERAL");
23226            self.write_space();
23227        }
23228
23229        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
23230        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
23231        // to carry the LIMIT modifier without adding more parens
23232        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
23233            matches!(
23234                &p.this,
23235                Expression::Select(_)
23236                    | Expression::Union(_)
23237                    | Expression::Intersect(_)
23238                    | Expression::Except(_)
23239                    | Expression::Subquery(_)
23240            )
23241        } else {
23242            false
23243        };
23244
23245        // Check if inner expression is a statement for pretty formatting
23246        let is_statement = matches!(
23247            &subquery.this,
23248            Expression::Select(_)
23249                | Expression::Union(_)
23250                | Expression::Intersect(_)
23251                | Expression::Except(_)
23252                | Expression::Merge(_)
23253        );
23254
23255        if !skip_outer_parens {
23256            self.write("(");
23257            if self.config.pretty && is_statement {
23258                self.write_newline();
23259                self.indent_level += 1;
23260                self.write_indent();
23261            }
23262        }
23263        self.generate_expression(&subquery.this)?;
23264
23265        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
23266        if subquery.modifiers_inside {
23267            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
23268            if let Some(order_by) = &subquery.order_by {
23269                self.write_space();
23270                self.write_keyword("ORDER BY");
23271                self.write_space();
23272                for (i, ord) in order_by.expressions.iter().enumerate() {
23273                    if i > 0 {
23274                        self.write(", ");
23275                    }
23276                    self.generate_ordered(ord)?;
23277                }
23278            }
23279
23280            if let Some(limit) = &subquery.limit {
23281                self.write_space();
23282                self.write_keyword("LIMIT");
23283                self.write_space();
23284                self.generate_expression(&limit.this)?;
23285                if limit.percent {
23286                    self.write_space();
23287                    self.write_keyword("PERCENT");
23288                }
23289            }
23290
23291            if let Some(offset) = &subquery.offset {
23292                self.write_space();
23293                self.write_keyword("OFFSET");
23294                self.write_space();
23295                self.generate_expression(&offset.this)?;
23296            }
23297        }
23298
23299        if !skip_outer_parens {
23300            if self.config.pretty && is_statement {
23301                self.write_newline();
23302                self.indent_level -= 1;
23303                self.write_indent();
23304            }
23305            self.write(")");
23306        }
23307
23308        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
23309        if !subquery.modifiers_inside {
23310            if let Some(order_by) = &subquery.order_by {
23311                self.write_space();
23312                self.write_keyword("ORDER BY");
23313                self.write_space();
23314                for (i, ord) in order_by.expressions.iter().enumerate() {
23315                    if i > 0 {
23316                        self.write(", ");
23317                    }
23318                    self.generate_ordered(ord)?;
23319                }
23320            }
23321
23322            if let Some(limit) = &subquery.limit {
23323                self.write_space();
23324                self.write_keyword("LIMIT");
23325                self.write_space();
23326                self.generate_expression(&limit.this)?;
23327                if limit.percent {
23328                    self.write_space();
23329                    self.write_keyword("PERCENT");
23330                }
23331            }
23332
23333            if let Some(offset) = &subquery.offset {
23334                self.write_space();
23335                self.write_keyword("OFFSET");
23336                self.write_space();
23337                self.generate_expression(&offset.this)?;
23338            }
23339
23340            // Generate DISTRIBUTE BY (Hive/Spark)
23341            if let Some(distribute_by) = &subquery.distribute_by {
23342                self.write_space();
23343                self.write_keyword("DISTRIBUTE BY");
23344                self.write_space();
23345                for (i, expr) in distribute_by.expressions.iter().enumerate() {
23346                    if i > 0 {
23347                        self.write(", ");
23348                    }
23349                    self.generate_expression(expr)?;
23350                }
23351            }
23352
23353            // Generate SORT BY (Hive/Spark)
23354            if let Some(sort_by) = &subquery.sort_by {
23355                self.write_space();
23356                self.write_keyword("SORT BY");
23357                self.write_space();
23358                for (i, ord) in sort_by.expressions.iter().enumerate() {
23359                    if i > 0 {
23360                        self.write(", ");
23361                    }
23362                    self.generate_ordered(ord)?;
23363                }
23364            }
23365
23366            // Generate CLUSTER BY (Hive/Spark)
23367            if let Some(cluster_by) = &subquery.cluster_by {
23368                self.write_space();
23369                self.write_keyword("CLUSTER BY");
23370                self.write_space();
23371                for (i, ord) in cluster_by.expressions.iter().enumerate() {
23372                    if i > 0 {
23373                        self.write(", ");
23374                    }
23375                    self.generate_ordered(ord)?;
23376                }
23377            }
23378        }
23379
23380        if let Some(alias) = &subquery.alias {
23381            self.write_space();
23382            let skip_as = matches!(self.config.dialect, Some(DialectType::Oracle))
23383                || (matches!(self.config.dialect, Some(DialectType::ClickHouse))
23384                    && !subquery.alias_explicit_as);
23385            if !skip_as {
23386                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23387                    self.write(subquery.alias_keyword.as_deref().unwrap_or("AS"));
23388                } else {
23389                    self.write_keyword("AS");
23390                }
23391                self.write_space();
23392            }
23393            self.generate_identifier(alias)?;
23394            if !subquery.column_aliases.is_empty() {
23395                self.write("(");
23396                for (i, col) in subquery.column_aliases.iter().enumerate() {
23397                    if i > 0 {
23398                        self.write(", ");
23399                    }
23400                    self.generate_identifier(col)?;
23401                }
23402                self.write(")");
23403            }
23404        }
23405        // Output trailing comments
23406        for comment in &subquery.trailing_comments {
23407            self.write(" ");
23408            self.write_formatted_comment(comment);
23409        }
23410        Ok(())
23411    }
23412
23413    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
23414        // Generate WITH clause if present
23415        if let Some(ref with) = pivot.with {
23416            self.generate_with(with)?;
23417            self.write_space();
23418        }
23419
23420        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
23421
23422        // Check for Redshift UNPIVOT in FROM clause:
23423        // UNPIVOT expr [AS val AT attr]
23424        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
23425        let is_redshift_unpivot = pivot.unpivot
23426            && pivot.expressions.is_empty()
23427            && pivot.fields.is_empty()
23428            && pivot.using.is_empty()
23429            && pivot.into.is_none()
23430            && !matches!(&pivot.this, Expression::Null(_));
23431
23432        if is_redshift_unpivot {
23433            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
23434            self.write_keyword("UNPIVOT");
23435            self.write_space();
23436            self.generate_expression(&pivot.this)?;
23437            // Alias - for Redshift it can be "val AT attr" format
23438            if let Some(alias) = &pivot.alias {
23439                self.write_space();
23440                self.write_keyword("AS");
23441                self.write_space();
23442                // The alias might contain " AT " for the attr part
23443                self.write(&alias.name);
23444            }
23445            return Ok(());
23446        }
23447
23448        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
23449        let is_simplified = !pivot.using.is_empty()
23450            || pivot.into.is_some()
23451            || (pivot.fields.is_empty()
23452                && !pivot.expressions.is_empty()
23453                && !matches!(&pivot.this, Expression::Null(_)));
23454
23455        if is_simplified {
23456            // DuckDB simplified syntax:
23457            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
23458            //   UNPIVOT table ON cols INTO NAME col VALUE col
23459            self.write_keyword(direction);
23460            self.write_space();
23461            self.generate_expression(&pivot.this)?;
23462
23463            if !pivot.expressions.is_empty() {
23464                self.write_space();
23465                self.write_keyword("ON");
23466                self.write_space();
23467                for (i, expr) in pivot.expressions.iter().enumerate() {
23468                    if i > 0 {
23469                        self.write(", ");
23470                    }
23471                    self.generate_expression(expr)?;
23472                }
23473            }
23474
23475            // INTO (for UNPIVOT)
23476            if let Some(into) = &pivot.into {
23477                self.write_space();
23478                self.write_keyword("INTO");
23479                self.write_space();
23480                self.generate_expression(into)?;
23481            }
23482
23483            // USING (for PIVOT)
23484            if !pivot.using.is_empty() {
23485                self.write_space();
23486                self.write_keyword("USING");
23487                self.write_space();
23488                for (i, expr) in pivot.using.iter().enumerate() {
23489                    if i > 0 {
23490                        self.write(", ");
23491                    }
23492                    self.generate_expression(expr)?;
23493                }
23494            }
23495
23496            // GROUP BY
23497            if let Some(group) = &pivot.group {
23498                self.write_space();
23499                self.generate_expression(group)?;
23500            }
23501        } else {
23502            // Standard syntax:
23503            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
23504            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
23505            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
23506            if !matches!(&pivot.this, Expression::Null(_)) {
23507                self.generate_expression(&pivot.this)?;
23508                self.write_space();
23509            }
23510            self.write_keyword(direction);
23511            self.write("(");
23512
23513            // Aggregation expressions
23514            for (i, expr) in pivot.expressions.iter().enumerate() {
23515                if i > 0 {
23516                    self.write(", ");
23517                }
23518                self.generate_expression(expr)?;
23519            }
23520
23521            // FOR...IN fields
23522            if !pivot.fields.is_empty() {
23523                if !pivot.expressions.is_empty() {
23524                    self.write_space();
23525                }
23526                self.write_keyword("FOR");
23527                self.write_space();
23528                for (i, field) in pivot.fields.iter().enumerate() {
23529                    if i > 0 {
23530                        self.write_space();
23531                    }
23532                    // field is an In expression: column IN (values)
23533                    self.generate_expression(field)?;
23534                }
23535            }
23536
23537            // DEFAULT ON NULL
23538            if let Some(default_val) = &pivot.default_on_null {
23539                self.write_space();
23540                self.write_keyword("DEFAULT ON NULL");
23541                self.write(" (");
23542                self.generate_expression(default_val)?;
23543                self.write(")");
23544            }
23545
23546            // GROUP BY inside PIVOT parens
23547            if let Some(group) = &pivot.group {
23548                self.write_space();
23549                self.generate_expression(group)?;
23550            }
23551
23552            self.write(")");
23553        }
23554
23555        // Alias
23556        if let Some(alias) = &pivot.alias {
23557            self.write_space();
23558            self.write_keyword("AS");
23559            self.write_space();
23560            self.generate_identifier(alias)?;
23561            self.generate_alias_column_list(&pivot.alias_columns)?;
23562        }
23563
23564        Ok(())
23565    }
23566
23567    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
23568        self.generate_expression(&unpivot.this)?;
23569        self.write_space();
23570        self.write_keyword("UNPIVOT");
23571        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
23572        if let Some(include) = unpivot.include_nulls {
23573            self.write_space();
23574            if include {
23575                self.write_keyword("INCLUDE NULLS");
23576            } else {
23577                self.write_keyword("EXCLUDE NULLS");
23578            }
23579            self.write_space();
23580        }
23581        self.write("(");
23582        if unpivot.value_column_parenthesized {
23583            self.write("(");
23584        }
23585        self.generate_identifier(&unpivot.value_column)?;
23586        // Output additional value columns if present
23587        for extra_col in &unpivot.extra_value_columns {
23588            self.write(", ");
23589            self.generate_identifier(extra_col)?;
23590        }
23591        if unpivot.value_column_parenthesized {
23592            self.write(")");
23593        }
23594        self.write_space();
23595        self.write_keyword("FOR");
23596        self.write_space();
23597        self.generate_identifier(&unpivot.name_column)?;
23598        self.write_space();
23599        self.write_keyword("IN");
23600        self.write(" (");
23601        for (i, col) in unpivot.columns.iter().enumerate() {
23602            if i > 0 {
23603                self.write(", ");
23604            }
23605            self.generate_expression(col)?;
23606        }
23607        self.write("))");
23608        if let Some(alias) = &unpivot.alias {
23609            self.write_space();
23610            self.write_keyword("AS");
23611            self.write_space();
23612            self.generate_identifier(alias)?;
23613            self.generate_alias_column_list(&unpivot.alias_columns)?;
23614        }
23615        Ok(())
23616    }
23617
23618    fn generate_alias_column_list(&mut self, columns: &[Identifier]) -> Result<()> {
23619        if columns.is_empty() {
23620            return Ok(());
23621        }
23622
23623        self.write("(");
23624        for (i, column) in columns.iter().enumerate() {
23625            if i > 0 {
23626                self.write(", ");
23627            }
23628            self.generate_identifier(column)?;
23629        }
23630        self.write(")");
23631        Ok(())
23632    }
23633
23634    fn generate_values(&mut self, values: &Values) -> Result<()> {
23635        self.write_keyword("VALUES");
23636        for (i, row) in values.expressions.iter().enumerate() {
23637            if i > 0 {
23638                self.write(",");
23639            }
23640            self.write(" (");
23641            for (j, expr) in row.expressions.iter().enumerate() {
23642                if j > 0 {
23643                    self.write(", ");
23644                }
23645                self.generate_expression(expr)?;
23646            }
23647            self.write(")");
23648        }
23649        if let Some(alias) = &values.alias {
23650            self.write_space();
23651            self.write_keyword("AS");
23652            self.write_space();
23653            self.generate_identifier(alias)?;
23654            if !values.column_aliases.is_empty() {
23655                self.write("(");
23656                for (i, col) in values.column_aliases.iter().enumerate() {
23657                    if i > 0 {
23658                        self.write(", ");
23659                    }
23660                    self.generate_identifier(col)?;
23661                }
23662                self.write(")");
23663            }
23664        }
23665        Ok(())
23666    }
23667
23668    fn generate_array(&mut self, arr: &Array) -> Result<()> {
23669        // Apply struct name inheritance for target dialects that need it
23670        let needs_inheritance = matches!(
23671            self.config.dialect,
23672            Some(DialectType::DuckDB)
23673                | Some(DialectType::Spark)
23674                | Some(DialectType::Databricks)
23675                | Some(DialectType::Hive)
23676                | Some(DialectType::Snowflake)
23677                | Some(DialectType::Presto)
23678                | Some(DialectType::Trino)
23679        );
23680        let propagated: Vec<Expression>;
23681        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
23682            propagated = Self::inherit_struct_field_names(&arr.expressions);
23683            &propagated
23684        } else {
23685            &arr.expressions
23686        };
23687
23688        // Generic mode: ARRAY(1, 2, 3) with parentheses
23689        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
23690        let use_parens =
23691            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
23692        if !self.config.array_bracket_only {
23693            self.write_keyword("ARRAY");
23694        }
23695        if use_parens {
23696            self.write("(");
23697        } else {
23698            self.write("[");
23699        }
23700        for (i, expr) in expressions.iter().enumerate() {
23701            if i > 0 {
23702                self.write(", ");
23703            }
23704            self.generate_expression(expr)?;
23705        }
23706        if use_parens {
23707            self.write(")");
23708        } else {
23709            self.write("]");
23710        }
23711        Ok(())
23712    }
23713
23714    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
23715        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
23716        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
23717        if tuple.expressions.len() == 2 {
23718            if let Expression::TableAlias(_) = &tuple.expressions[1] {
23719                // First element is the function/expression, second is the TableAlias
23720                self.generate_expression(&tuple.expressions[0])?;
23721                self.write_space();
23722                self.write_keyword("AS");
23723                self.write_space();
23724                self.generate_expression(&tuple.expressions[1])?;
23725                return Ok(());
23726            }
23727        }
23728
23729        // In pretty mode, format long tuples with each element on a new line
23730        // Only expand if total width exceeds threshold
23731        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
23732            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
23733            for expr in &tuple.expressions {
23734                expr_strings.push(self.generate_to_string(expr)?);
23735            }
23736            self.too_wide(&expr_strings)
23737        } else {
23738            false
23739        };
23740
23741        if expand_tuple {
23742            self.write("(");
23743            self.write_newline();
23744            self.indent_level += 1;
23745            for (i, expr) in tuple.expressions.iter().enumerate() {
23746                if i > 0 {
23747                    self.write(",");
23748                    self.write_newline();
23749                }
23750                self.write_indent();
23751                self.generate_expression(expr)?;
23752            }
23753            self.indent_level -= 1;
23754            self.write_newline();
23755            self.write_indent();
23756            self.write(")");
23757        } else {
23758            self.write("(");
23759            for (i, expr) in tuple.expressions.iter().enumerate() {
23760                if i > 0 {
23761                    self.write(", ");
23762                }
23763                self.generate_expression(expr)?;
23764            }
23765            self.write(")");
23766        }
23767        Ok(())
23768    }
23769
23770    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
23771        self.generate_expression(&pipe.this)?;
23772        self.write(" |> ");
23773        self.generate_expression(&pipe.expression)?;
23774        Ok(())
23775    }
23776
23777    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
23778        let unsupported_tsql_null_ordering = ordered.nulls_first.is_some()
23779            && !self.config.null_ordering_supported
23780            && matches!(
23781                self.config.dialect,
23782                Some(DialectType::TSQL) | Some(DialectType::Fabric)
23783            );
23784        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
23785        let emulate_tsql_null_ordering = if let Some(nulls_first) = ordered.nulls_first {
23786            let target_default_nulls_first = !ordered.desc;
23787
23788            unsupported_tsql_null_ordering
23789                && nulls_first != target_default_nulls_first
23790                && !random_ordering
23791        } else {
23792            false
23793        };
23794
23795        if emulate_tsql_null_ordering {
23796            self.write_keyword("CASE WHEN");
23797            self.write_space();
23798            self.generate_expression(&ordered.this)?;
23799            self.write_space();
23800            self.write_keyword("IS NULL THEN 1 ELSE 0 END");
23801            if ordered.nulls_first == Some(true) {
23802                self.write_space();
23803                self.write_keyword("DESC");
23804            }
23805            self.write(", ");
23806        }
23807
23808        self.generate_expression(&ordered.this)?;
23809        if ordered.desc {
23810            self.write_space();
23811            self.write_keyword("DESC");
23812        } else if ordered.explicit_asc {
23813            self.write_space();
23814            self.write_keyword("ASC");
23815        }
23816        if let Some(nulls_first) = ordered.nulls_first {
23817            if !unsupported_tsql_null_ordering
23818                && (self.config.null_ordering_supported
23819                    || !matches!(self.config.dialect, Some(DialectType::Fabric)))
23820            {
23821                // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
23822                // for the dialect. Different dialects have different NULL ordering defaults:
23823                //
23824                // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
23825                //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
23826                //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
23827                //
23828                // nulls_are_small (Spark, Hive, BigQuery, most others):
23829                //   - ASC: NULLS FIRST is default
23830                //   - DESC: NULLS LAST is default
23831                //
23832                // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
23833                //   - NULLS LAST is always the default regardless of sort direction
23834                let is_asc = !ordered.desc;
23835                let is_nulls_are_large = matches!(
23836                    self.config.dialect,
23837                    Some(DialectType::Oracle)
23838                        | Some(DialectType::PostgreSQL)
23839                        | Some(DialectType::Redshift)
23840                        | Some(DialectType::Snowflake)
23841                );
23842                let is_nulls_are_last = matches!(
23843                    self.config.dialect,
23844                    Some(DialectType::Dremio)
23845                        | Some(DialectType::DuckDB)
23846                        | Some(DialectType::Presto)
23847                        | Some(DialectType::Trino)
23848                        | Some(DialectType::Athena)
23849                        | Some(DialectType::ClickHouse)
23850                        | Some(DialectType::Drill)
23851                        | Some(DialectType::Exasol)
23852                );
23853
23854                // Check if the NULLS ordering matches the default for this dialect
23855                let is_default_nulls = if is_nulls_are_large {
23856                    // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
23857                    (is_asc && !nulls_first) || (!is_asc && nulls_first)
23858                } else if is_nulls_are_last {
23859                    // For nulls_are_last: NULLS LAST is always default
23860                    !nulls_first
23861                } else {
23862                    false
23863                };
23864
23865                if !is_default_nulls {
23866                    self.write_space();
23867                    self.write_keyword("NULLS");
23868                    self.write_space();
23869                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
23870                }
23871            }
23872        }
23873        // WITH FILL clause (ClickHouse)
23874        if let Some(ref with_fill) = ordered.with_fill {
23875            self.write_space();
23876            self.generate_with_fill(with_fill)?;
23877        }
23878        Ok(())
23879    }
23880
23881    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
23882    fn write_clickhouse_type(&mut self, type_str: &str) {
23883        if self.clickhouse_nullable_depth < 0 {
23884            // Map key context: don't wrap in Nullable
23885            self.write(type_str);
23886        } else {
23887            self.write(&format!("Nullable({})", type_str));
23888        }
23889    }
23890
23891    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
23892        use crate::dialects::DialectType;
23893
23894        match dt {
23895            DataType::Boolean => {
23896                // Dialect-specific boolean type mappings
23897                match self.config.dialect {
23898                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
23899                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
23900                    Some(DialectType::Oracle) => {
23901                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
23902                        self.write_keyword("NUMBER(1)")
23903                    }
23904                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
23905                    _ => self.write_keyword("BOOLEAN"),
23906                }
23907            }
23908            DataType::TinyInt { length } => {
23909                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
23910                // Dremio maps TINYINT to INT
23911                // ClickHouse maps TINYINT to Int8
23912                match self.config.dialect {
23913                    Some(DialectType::PostgreSQL)
23914                    | Some(DialectType::Redshift)
23915                    | Some(DialectType::Oracle)
23916                    | Some(DialectType::Exasol) => {
23917                        self.write_keyword("SMALLINT");
23918                    }
23919                    Some(DialectType::Teradata) => {
23920                        // Teradata uses BYTEINT for smallest integer
23921                        self.write_keyword("BYTEINT");
23922                    }
23923                    Some(DialectType::Dremio) => {
23924                        // Dremio maps TINYINT to INT
23925                        self.write_keyword("INT");
23926                    }
23927                    Some(DialectType::ClickHouse) => {
23928                        self.write_clickhouse_type("Int8");
23929                    }
23930                    _ => {
23931                        self.write_keyword("TINYINT");
23932                    }
23933                }
23934                if let Some(n) = length {
23935                    if !matches!(
23936                        self.config.dialect,
23937                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
23938                    ) {
23939                        self.write(&format!("({})", n));
23940                    }
23941                }
23942            }
23943            DataType::SmallInt { length } => {
23944                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
23945                match self.config.dialect {
23946                    Some(DialectType::Dremio) => {
23947                        self.write_keyword("INT");
23948                    }
23949                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
23950                        self.write_keyword("INTEGER");
23951                    }
23952                    Some(DialectType::BigQuery) => {
23953                        self.write_keyword("INT64");
23954                    }
23955                    Some(DialectType::ClickHouse) => {
23956                        self.write_clickhouse_type("Int16");
23957                    }
23958                    _ => {
23959                        self.write_keyword("SMALLINT");
23960                        if let Some(n) = length {
23961                            self.write(&format!("({})", n));
23962                        }
23963                    }
23964                }
23965            }
23966            DataType::Int {
23967                length,
23968                integer_spelling: _,
23969            } => {
23970                // BigQuery uses INT64 for INT
23971                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
23972                    self.write_keyword("INT64");
23973                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23974                    self.write_clickhouse_type("Int32");
23975                } else {
23976                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
23977                    let use_integer = match self.config.dialect {
23978                        Some(DialectType::TSQL)
23979                        | Some(DialectType::Fabric)
23980                        | Some(DialectType::Presto)
23981                        | Some(DialectType::Trino)
23982                        | Some(DialectType::SQLite)
23983                        | Some(DialectType::Redshift) => true,
23984                        _ => false,
23985                    };
23986                    if use_integer {
23987                        self.write_keyword("INTEGER");
23988                    } else {
23989                        self.write_keyword("INT");
23990                    }
23991                    if let Some(n) = length {
23992                        self.write(&format!("({})", n));
23993                    }
23994                }
23995            }
23996            DataType::BigInt { length } => {
23997                // Dialect-specific bigint type mappings
23998                match self.config.dialect {
23999                    Some(DialectType::Oracle) => {
24000                        // Oracle doesn't have BIGINT, uses INT
24001                        self.write_keyword("INT");
24002                    }
24003                    Some(DialectType::ClickHouse) => {
24004                        self.write_clickhouse_type("Int64");
24005                    }
24006                    _ => {
24007                        self.write_keyword("BIGINT");
24008                        if let Some(n) = length {
24009                            self.write(&format!("({})", n));
24010                        }
24011                    }
24012                }
24013            }
24014            DataType::Float {
24015                precision,
24016                scale,
24017                real_spelling,
24018            } => {
24019                // Dialect-specific float type mappings
24020                // If real_spelling is true, preserve REAL; otherwise use dialect default
24021                // Spark/Hive don't support REAL, always use FLOAT
24022                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24023                    self.write_clickhouse_type("Float32");
24024                } else if *real_spelling
24025                    && !matches!(
24026                        self.config.dialect,
24027                        Some(DialectType::Spark)
24028                            | Some(DialectType::Databricks)
24029                            | Some(DialectType::Hive)
24030                            | Some(DialectType::Snowflake)
24031                            | Some(DialectType::MySQL)
24032                            | Some(DialectType::BigQuery)
24033                    )
24034                {
24035                    self.write_keyword("REAL")
24036                } else {
24037                    match self.config.dialect {
24038                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
24039                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
24040                        _ => self.write_keyword("FLOAT"),
24041                    }
24042                }
24043                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
24044                // Spark/Hive don't support FLOAT(precision)
24045                if !matches!(
24046                    self.config.dialect,
24047                    Some(DialectType::Spark)
24048                        | Some(DialectType::Databricks)
24049                        | Some(DialectType::Hive)
24050                        | Some(DialectType::Presto)
24051                        | Some(DialectType::Trino)
24052                ) {
24053                    if let Some(p) = precision {
24054                        self.write(&format!("({}", p));
24055                        if let Some(s) = scale {
24056                            self.write(&format!(", {})", s));
24057                        } else {
24058                            self.write(")");
24059                        }
24060                    }
24061                }
24062            }
24063            DataType::Double { precision, scale } => {
24064                // Dialect-specific double type mappings
24065                match self.config.dialect {
24066                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24067                        self.write_keyword("FLOAT")
24068                    } // SQL Server/Fabric FLOAT is double
24069                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
24070                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
24071                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
24072                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
24073                    Some(DialectType::PostgreSQL)
24074                    | Some(DialectType::Redshift)
24075                    | Some(DialectType::Teradata)
24076                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
24077                    _ => self.write_keyword("DOUBLE"),
24078                }
24079                // MySQL supports DOUBLE(precision, scale)
24080                if let Some(p) = precision {
24081                    self.write(&format!("({}", p));
24082                    if let Some(s) = scale {
24083                        self.write(&format!(", {})", s));
24084                    } else {
24085                        self.write(")");
24086                    }
24087                }
24088            }
24089            DataType::Decimal { precision, scale } => {
24090                // Dialect-specific decimal type mappings
24091                match self.config.dialect {
24092                    Some(DialectType::ClickHouse) => {
24093                        self.write("Decimal");
24094                        if let Some(p) = precision {
24095                            self.write(&format!("({}", p));
24096                            if let Some(s) = scale {
24097                                self.write(&format!(", {}", s));
24098                            }
24099                            self.write(")");
24100                        }
24101                    }
24102                    Some(DialectType::Oracle) => {
24103                        // Oracle uses NUMBER instead of DECIMAL
24104                        self.write_keyword("NUMBER");
24105                        if let Some(p) = precision {
24106                            self.write(&format!("({}", p));
24107                            if let Some(s) = scale {
24108                                self.write(&format!(", {}", s));
24109                            }
24110                            self.write(")");
24111                        }
24112                    }
24113                    Some(DialectType::BigQuery) => {
24114                        // BigQuery uses NUMERIC instead of DECIMAL
24115                        self.write_keyword("NUMERIC");
24116                        if let Some(p) = precision {
24117                            self.write(&format!("({}", p));
24118                            if let Some(s) = scale {
24119                                self.write(&format!(", {}", s));
24120                            }
24121                            self.write(")");
24122                        }
24123                    }
24124                    _ => {
24125                        self.write_keyword("DECIMAL");
24126                        if let Some(p) = precision {
24127                            self.write(&format!("({}", p));
24128                            if let Some(s) = scale {
24129                                self.write(&format!(", {}", s));
24130                            }
24131                            self.write(")");
24132                        }
24133                    }
24134                }
24135            }
24136            DataType::Char { length } => {
24137                // Dialect-specific char type mappings
24138                match self.config.dialect {
24139                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24140                        // DuckDB/SQLite maps CHAR to TEXT
24141                        self.write_keyword("TEXT");
24142                    }
24143                    Some(DialectType::Hive)
24144                    | Some(DialectType::Spark)
24145                    | Some(DialectType::Databricks) => {
24146                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
24147                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
24148                        if length.is_some()
24149                            && !matches!(self.config.dialect, Some(DialectType::Hive))
24150                        {
24151                            self.write_keyword("CHAR");
24152                            if let Some(n) = length {
24153                                self.write(&format!("({})", n));
24154                            }
24155                        } else {
24156                            self.write_keyword("STRING");
24157                        }
24158                    }
24159                    Some(DialectType::Dremio) => {
24160                        // Dremio maps CHAR to VARCHAR
24161                        self.write_keyword("VARCHAR");
24162                        if let Some(n) = length {
24163                            self.write(&format!("({})", n));
24164                        }
24165                    }
24166                    _ => {
24167                        self.write_keyword("CHAR");
24168                        if let Some(n) = length {
24169                            self.write(&format!("({})", n));
24170                        }
24171                    }
24172                }
24173            }
24174            DataType::VarChar {
24175                length,
24176                parenthesized_length,
24177            } => {
24178                // Dialect-specific varchar type mappings
24179                match self.config.dialect {
24180                    Some(DialectType::Oracle) => {
24181                        self.write_keyword("VARCHAR2");
24182                        if let Some(n) = length {
24183                            self.write(&format!("({})", n));
24184                        }
24185                    }
24186                    Some(DialectType::DuckDB) => {
24187                        // DuckDB maps VARCHAR to TEXT, preserving length
24188                        self.write_keyword("TEXT");
24189                        if let Some(n) = length {
24190                            self.write(&format!("({})", n));
24191                        }
24192                    }
24193                    Some(DialectType::SQLite) => {
24194                        // SQLite maps VARCHAR to TEXT, preserving length
24195                        self.write_keyword("TEXT");
24196                        if let Some(n) = length {
24197                            self.write(&format!("({})", n));
24198                        }
24199                    }
24200                    Some(DialectType::MySQL) if length.is_none() => {
24201                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
24202                        self.write_keyword("TEXT");
24203                    }
24204                    Some(DialectType::Hive)
24205                    | Some(DialectType::Spark)
24206                    | Some(DialectType::Databricks)
24207                        if length.is_none() =>
24208                    {
24209                        // Hive/Spark/Databricks: VARCHAR without length → STRING
24210                        self.write_keyword("STRING");
24211                    }
24212                    _ => {
24213                        self.write_keyword("VARCHAR");
24214                        if let Some(n) = length {
24215                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
24216                            if *parenthesized_length {
24217                                self.write(&format!("(({}))", n));
24218                            } else {
24219                                self.write(&format!("({})", n));
24220                            }
24221                        }
24222                    }
24223                }
24224            }
24225            DataType::Text => {
24226                // Dialect-specific text type mappings
24227                match self.config.dialect {
24228                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
24229                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24230                        self.write_keyword("VARCHAR(MAX)")
24231                    }
24232                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
24233                    Some(DialectType::Snowflake)
24234                    | Some(DialectType::Dremio)
24235                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
24236                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
24237                    Some(DialectType::Presto)
24238                    | Some(DialectType::Trino)
24239                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
24240                    Some(DialectType::Spark)
24241                    | Some(DialectType::Databricks)
24242                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
24243                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
24244                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
24245                        self.write_keyword("STRING")
24246                    }
24247                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
24248                    _ => self.write_keyword("TEXT"),
24249                }
24250            }
24251            DataType::TextWithLength { length } => {
24252                // TEXT(n) - dialect-specific type with length
24253                match self.config.dialect {
24254                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
24255                    Some(DialectType::Hive)
24256                    | Some(DialectType::Spark)
24257                    | Some(DialectType::Databricks) => {
24258                        self.write(&format!("VARCHAR({})", length));
24259                    }
24260                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
24261                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
24262                    Some(DialectType::Snowflake)
24263                    | Some(DialectType::Presto)
24264                    | Some(DialectType::Trino)
24265                    | Some(DialectType::Athena)
24266                    | Some(DialectType::Drill)
24267                    | Some(DialectType::Dremio) => {
24268                        self.write(&format!("VARCHAR({})", length));
24269                    }
24270                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24271                        self.write(&format!("VARCHAR({})", length))
24272                    }
24273                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
24274                        self.write(&format!("STRING({})", length))
24275                    }
24276                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
24277                    _ => self.write(&format!("TEXT({})", length)),
24278                }
24279            }
24280            DataType::String { length } => {
24281                // STRING type with optional length (BigQuery STRING(n))
24282                match self.config.dialect {
24283                    Some(DialectType::ClickHouse) => {
24284                        // ClickHouse uses String with specific casing
24285                        self.write("String");
24286                        if let Some(n) = length {
24287                            self.write(&format!("({})", n));
24288                        }
24289                    }
24290                    Some(DialectType::BigQuery)
24291                    | Some(DialectType::Hive)
24292                    | Some(DialectType::Spark)
24293                    | Some(DialectType::Databricks)
24294                    | Some(DialectType::StarRocks)
24295                    | Some(DialectType::Doris) => {
24296                        self.write_keyword("STRING");
24297                        if let Some(n) = length {
24298                            self.write(&format!("({})", n));
24299                        }
24300                    }
24301                    Some(DialectType::PostgreSQL) => {
24302                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
24303                        if let Some(n) = length {
24304                            self.write_keyword("VARCHAR");
24305                            self.write(&format!("({})", n));
24306                        } else {
24307                            self.write_keyword("TEXT");
24308                        }
24309                    }
24310                    Some(DialectType::Redshift) => {
24311                        // Redshift: STRING -> VARCHAR(MAX)
24312                        if let Some(n) = length {
24313                            self.write_keyword("VARCHAR");
24314                            self.write(&format!("({})", n));
24315                        } else {
24316                            self.write_keyword("VARCHAR(MAX)");
24317                        }
24318                    }
24319                    Some(DialectType::MySQL) => {
24320                        // MySQL doesn't have STRING - use VARCHAR or TEXT
24321                        if let Some(n) = length {
24322                            self.write_keyword("VARCHAR");
24323                            self.write(&format!("({})", n));
24324                        } else {
24325                            self.write_keyword("TEXT");
24326                        }
24327                    }
24328                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24329                        // TSQL: STRING -> VARCHAR(MAX)
24330                        if let Some(n) = length {
24331                            self.write_keyword("VARCHAR");
24332                            self.write(&format!("({})", n));
24333                        } else {
24334                            self.write_keyword("VARCHAR(MAX)");
24335                        }
24336                    }
24337                    Some(DialectType::Oracle) => {
24338                        // Oracle: STRING -> CLOB
24339                        self.write_keyword("CLOB");
24340                    }
24341                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
24342                        // DuckDB/Materialize uses TEXT for string types
24343                        self.write_keyword("TEXT");
24344                        if let Some(n) = length {
24345                            self.write(&format!("({})", n));
24346                        }
24347                    }
24348                    Some(DialectType::Presto)
24349                    | Some(DialectType::Trino)
24350                    | Some(DialectType::Drill)
24351                    | Some(DialectType::Dremio) => {
24352                        // Presto/Trino/Drill use VARCHAR for string types
24353                        self.write_keyword("VARCHAR");
24354                        if let Some(n) = length {
24355                            self.write(&format!("({})", n));
24356                        }
24357                    }
24358                    Some(DialectType::Snowflake) => {
24359                        // Snowflake: STRING stays as STRING (identity/DDL)
24360                        // CAST context STRING -> VARCHAR is handled in generate_cast
24361                        self.write_keyword("STRING");
24362                        if let Some(n) = length {
24363                            self.write(&format!("({})", n));
24364                        }
24365                    }
24366                    _ => {
24367                        // Default: output STRING with optional length
24368                        self.write_keyword("STRING");
24369                        if let Some(n) = length {
24370                            self.write(&format!("({})", n));
24371                        }
24372                    }
24373                }
24374            }
24375            DataType::Binary { length } => {
24376                // Dialect-specific binary type mappings
24377                match self.config.dialect {
24378                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
24379                        self.write_keyword("BYTEA");
24380                        if let Some(n) = length {
24381                            self.write(&format!("({})", n));
24382                        }
24383                    }
24384                    Some(DialectType::Redshift) => {
24385                        self.write_keyword("VARBYTE");
24386                        if let Some(n) = length {
24387                            self.write(&format!("({})", n));
24388                        }
24389                    }
24390                    Some(DialectType::DuckDB)
24391                    | Some(DialectType::SQLite)
24392                    | Some(DialectType::Oracle) => {
24393                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
24394                        self.write_keyword("BLOB");
24395                        if let Some(n) = length {
24396                            self.write(&format!("({})", n));
24397                        }
24398                    }
24399                    Some(DialectType::Presto)
24400                    | Some(DialectType::Trino)
24401                    | Some(DialectType::Athena)
24402                    | Some(DialectType::Drill)
24403                    | Some(DialectType::Dremio) => {
24404                        // These dialects map BINARY to VARBINARY
24405                        self.write_keyword("VARBINARY");
24406                        if let Some(n) = length {
24407                            self.write(&format!("({})", n));
24408                        }
24409                    }
24410                    Some(DialectType::ClickHouse) => {
24411                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
24412                        if self.clickhouse_nullable_depth < 0 {
24413                            self.write("BINARY");
24414                        } else {
24415                            self.write("Nullable(BINARY");
24416                        }
24417                        if let Some(n) = length {
24418                            self.write(&format!("({})", n));
24419                        }
24420                        if self.clickhouse_nullable_depth >= 0 {
24421                            self.write(")");
24422                        }
24423                    }
24424                    _ => {
24425                        self.write_keyword("BINARY");
24426                        if let Some(n) = length {
24427                            self.write(&format!("({})", n));
24428                        }
24429                    }
24430                }
24431            }
24432            DataType::VarBinary { length } => {
24433                // Dialect-specific varbinary type mappings
24434                match self.config.dialect {
24435                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
24436                        self.write_keyword("BYTEA");
24437                        if let Some(n) = length {
24438                            self.write(&format!("({})", n));
24439                        }
24440                    }
24441                    Some(DialectType::Redshift) => {
24442                        self.write_keyword("VARBYTE");
24443                        if let Some(n) = length {
24444                            self.write(&format!("({})", n));
24445                        }
24446                    }
24447                    Some(DialectType::DuckDB)
24448                    | Some(DialectType::SQLite)
24449                    | Some(DialectType::Oracle) => {
24450                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
24451                        self.write_keyword("BLOB");
24452                        if let Some(n) = length {
24453                            self.write(&format!("({})", n));
24454                        }
24455                    }
24456                    Some(DialectType::Exasol) => {
24457                        // Exasol maps VARBINARY to VARCHAR
24458                        self.write_keyword("VARCHAR");
24459                    }
24460                    Some(DialectType::Spark)
24461                    | Some(DialectType::Hive)
24462                    | Some(DialectType::Databricks) => {
24463                        // Spark/Hive use BINARY instead of VARBINARY
24464                        self.write_keyword("BINARY");
24465                        if let Some(n) = length {
24466                            self.write(&format!("({})", n));
24467                        }
24468                    }
24469                    Some(DialectType::ClickHouse) => {
24470                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
24471                        self.write_clickhouse_type("String");
24472                    }
24473                    _ => {
24474                        self.write_keyword("VARBINARY");
24475                        if let Some(n) = length {
24476                            self.write(&format!("({})", n));
24477                        }
24478                    }
24479                }
24480            }
24481            DataType::Blob => {
24482                // Dialect-specific blob type mappings
24483                match self.config.dialect {
24484                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
24485                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
24486                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24487                        self.write_keyword("VARBINARY")
24488                    }
24489                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
24490                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
24491                    Some(DialectType::Presto)
24492                    | Some(DialectType::Trino)
24493                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
24494                    Some(DialectType::DuckDB) => {
24495                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
24496                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
24497                        self.write_keyword("VARBINARY");
24498                    }
24499                    Some(DialectType::Spark)
24500                    | Some(DialectType::Databricks)
24501                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
24502                    Some(DialectType::ClickHouse) => {
24503                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
24504                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
24505                        // This matches Python sqlglot behavior.
24506                        self.write("Nullable(String)");
24507                    }
24508                    _ => self.write_keyword("BLOB"),
24509                }
24510            }
24511            DataType::Bit { length } => {
24512                // Dialect-specific bit type mappings
24513                match self.config.dialect {
24514                    Some(DialectType::Dremio)
24515                    | Some(DialectType::Spark)
24516                    | Some(DialectType::Databricks)
24517                    | Some(DialectType::Hive)
24518                    | Some(DialectType::Snowflake)
24519                    | Some(DialectType::BigQuery)
24520                    | Some(DialectType::Presto)
24521                    | Some(DialectType::Trino)
24522                    | Some(DialectType::ClickHouse)
24523                    | Some(DialectType::Redshift) => {
24524                        // These dialects don't support BIT type, use BOOLEAN
24525                        self.write_keyword("BOOLEAN");
24526                    }
24527                    _ => {
24528                        self.write_keyword("BIT");
24529                        if let Some(n) = length {
24530                            self.write(&format!("({})", n));
24531                        }
24532                    }
24533                }
24534            }
24535            DataType::VarBit { length } => {
24536                self.write_keyword("VARBIT");
24537                if let Some(n) = length {
24538                    self.write(&format!("({})", n));
24539                }
24540            }
24541            DataType::Date => self.write_keyword("DATE"),
24542            DataType::Time {
24543                precision,
24544                timezone,
24545            } => {
24546                if *timezone {
24547                    // Dialect-specific TIME WITH TIME ZONE output
24548                    match self.config.dialect {
24549                        Some(DialectType::DuckDB) => {
24550                            // DuckDB: TIMETZ (drops precision)
24551                            self.write_keyword("TIMETZ");
24552                        }
24553                        Some(DialectType::PostgreSQL) => {
24554                            // PostgreSQL: TIMETZ or TIMETZ(p)
24555                            self.write_keyword("TIMETZ");
24556                            if let Some(p) = precision {
24557                                self.write(&format!("({})", p));
24558                            }
24559                        }
24560                        _ => {
24561                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
24562                            self.write_keyword("TIME");
24563                            if let Some(p) = precision {
24564                                self.write(&format!("({})", p));
24565                            }
24566                            self.write_keyword(" WITH TIME ZONE");
24567                        }
24568                    }
24569                } else {
24570                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
24571                    if matches!(
24572                        self.config.dialect,
24573                        Some(DialectType::Spark)
24574                            | Some(DialectType::Databricks)
24575                            | Some(DialectType::Hive)
24576                    ) {
24577                        self.write_keyword("TIMESTAMP");
24578                    } else {
24579                        self.write_keyword("TIME");
24580                        if let Some(p) = precision {
24581                            self.write(&format!("({})", p));
24582                        }
24583                    }
24584                }
24585            }
24586            DataType::Timestamp {
24587                precision,
24588                timezone,
24589            } => {
24590                // Dialect-specific timestamp type mappings
24591                match self.config.dialect {
24592                    Some(DialectType::Snowflake) if *timezone => {
24593                        self.write_keyword("TIMESTAMPTZ");
24594                        if let Some(p) = precision {
24595                            self.write(&format!("({})", p));
24596                        }
24597                    }
24598                    Some(DialectType::ClickHouse) => {
24599                        self.write("DateTime");
24600                        if let Some(p) = precision {
24601                            self.write(&format!("({})", p));
24602                        }
24603                    }
24604                    Some(DialectType::TSQL) => {
24605                        if *timezone {
24606                            self.write_keyword("DATETIMEOFFSET");
24607                        } else {
24608                            self.write_keyword("DATETIME2");
24609                        }
24610                        if let Some(p) = precision {
24611                            self.write(&format!("({})", p));
24612                        }
24613                    }
24614                    Some(DialectType::MySQL) => {
24615                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
24616                        self.write_keyword("TIMESTAMP");
24617                        if let Some(p) = precision {
24618                            self.write(&format!("({})", p));
24619                        }
24620                    }
24621                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
24622                        // Doris/StarRocks: TIMESTAMP -> DATETIME
24623                        self.write_keyword("DATETIME");
24624                        if let Some(p) = precision {
24625                            self.write(&format!("({})", p));
24626                        }
24627                    }
24628                    Some(DialectType::BigQuery) => {
24629                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
24630                        if *timezone {
24631                            self.write_keyword("TIMESTAMP");
24632                        } else {
24633                            self.write_keyword("DATETIME");
24634                        }
24635                    }
24636                    Some(DialectType::DuckDB) => {
24637                        // DuckDB: TIMESTAMPTZ shorthand
24638                        if *timezone {
24639                            self.write_keyword("TIMESTAMPTZ");
24640                        } else {
24641                            self.write_keyword("TIMESTAMP");
24642                            if let Some(p) = precision {
24643                                self.write(&format!("({})", p));
24644                            }
24645                        }
24646                    }
24647                    _ => {
24648                        if *timezone && !self.config.tz_to_with_time_zone {
24649                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
24650                            self.write_keyword("TIMESTAMPTZ");
24651                            if let Some(p) = precision {
24652                                self.write(&format!("({})", p));
24653                            }
24654                        } else {
24655                            self.write_keyword("TIMESTAMP");
24656                            if let Some(p) = precision {
24657                                self.write(&format!("({})", p));
24658                            }
24659                            if *timezone {
24660                                self.write_space();
24661                                self.write_keyword("WITH TIME ZONE");
24662                            }
24663                        }
24664                    }
24665                }
24666            }
24667            DataType::Interval { unit, to } => {
24668                self.write_keyword("INTERVAL");
24669                if let Some(u) = unit {
24670                    self.write_space();
24671                    self.write_keyword(u);
24672                }
24673                // Handle range intervals like DAY TO HOUR
24674                if let Some(t) = to {
24675                    self.write_space();
24676                    self.write_keyword("TO");
24677                    self.write_space();
24678                    self.write_keyword(t);
24679                }
24680            }
24681            DataType::Json => {
24682                // Dialect-specific JSON type mappings
24683                match self.config.dialect {
24684                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
24685                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
24686                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
24687                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24688                    _ => self.write_keyword("JSON"),
24689                }
24690            }
24691            DataType::JsonB => {
24692                // JSONB is PostgreSQL specific, but Doris also supports it
24693                match self.config.dialect {
24694                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
24695                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
24696                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24697                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24698                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
24699                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
24700                }
24701            }
24702            DataType::Uuid => {
24703                // Dialect-specific UUID type mappings
24704                match self.config.dialect {
24705                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
24706                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
24707                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
24708                    Some(DialectType::BigQuery)
24709                    | Some(DialectType::Spark)
24710                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
24711                    _ => self.write_keyword("UUID"),
24712                }
24713            }
24714            DataType::Array {
24715                element_type,
24716                dimension,
24717            } => {
24718                // Dialect-specific array syntax
24719                match self.config.dialect {
24720                    Some(DialectType::PostgreSQL)
24721                    | Some(DialectType::Redshift)
24722                    | Some(DialectType::DuckDB) => {
24723                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
24724                        self.generate_data_type(element_type)?;
24725                        if let Some(dim) = dimension {
24726                            self.write(&format!("[{}]", dim));
24727                        } else {
24728                            self.write("[]");
24729                        }
24730                    }
24731                    Some(DialectType::BigQuery) => {
24732                        self.write_keyword("ARRAY<");
24733                        self.generate_data_type(element_type)?;
24734                        self.write(">");
24735                    }
24736                    Some(DialectType::Snowflake)
24737                    | Some(DialectType::Presto)
24738                    | Some(DialectType::Trino)
24739                    | Some(DialectType::ClickHouse) => {
24740                        // These dialects use Array(TYPE) parentheses syntax
24741                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24742                            self.write("Array(");
24743                        } else {
24744                            self.write_keyword("ARRAY(");
24745                        }
24746                        self.generate_data_type(element_type)?;
24747                        self.write(")");
24748                    }
24749                    Some(DialectType::TSQL)
24750                    | Some(DialectType::MySQL)
24751                    | Some(DialectType::Oracle) => {
24752                        // These dialects don't have native array types
24753                        // Fall back to JSON or use native workarounds
24754                        match self.config.dialect {
24755                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
24756                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24757                            _ => self.write_keyword("JSON"),
24758                        }
24759                    }
24760                    _ => {
24761                        // Default: use angle bracket syntax (ARRAY<T>)
24762                        self.write_keyword("ARRAY<");
24763                        self.generate_data_type(element_type)?;
24764                        self.write(">");
24765                    }
24766                }
24767            }
24768            DataType::List { element_type } => {
24769                // Materialize: element_type LIST (postfix syntax)
24770                self.generate_data_type(element_type)?;
24771                self.write_keyword(" LIST");
24772            }
24773            DataType::Map {
24774                key_type,
24775                value_type,
24776            } => {
24777                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
24778                match self.config.dialect {
24779                    Some(DialectType::Materialize) => {
24780                        // Materialize: MAP[key_type => value_type]
24781                        self.write_keyword("MAP[");
24782                        self.generate_data_type(key_type)?;
24783                        self.write(" => ");
24784                        self.generate_data_type(value_type)?;
24785                        self.write("]");
24786                    }
24787                    Some(DialectType::Snowflake)
24788                    | Some(DialectType::RisingWave)
24789                    | Some(DialectType::DuckDB)
24790                    | Some(DialectType::Presto)
24791                    | Some(DialectType::Trino)
24792                    | Some(DialectType::Athena) => {
24793                        self.write_keyword("MAP(");
24794                        self.generate_data_type(key_type)?;
24795                        self.write(", ");
24796                        self.generate_data_type(value_type)?;
24797                        self.write(")");
24798                    }
24799                    Some(DialectType::ClickHouse) => {
24800                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
24801                        // Key types must NOT be wrapped in Nullable
24802                        self.write("Map(");
24803                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
24804                        self.generate_data_type(key_type)?;
24805                        self.clickhouse_nullable_depth = 0;
24806                        self.write(", ");
24807                        self.generate_data_type(value_type)?;
24808                        self.write(")");
24809                    }
24810                    _ => {
24811                        self.write_keyword("MAP<");
24812                        self.generate_data_type(key_type)?;
24813                        self.write(", ");
24814                        self.generate_data_type(value_type)?;
24815                        self.write(">");
24816                    }
24817                }
24818            }
24819            DataType::Vector {
24820                element_type,
24821                dimension,
24822            } => {
24823                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
24824                    // SingleStore format: VECTOR(dimension, type_alias)
24825                    self.write_keyword("VECTOR(");
24826                    if let Some(dim) = dimension {
24827                        self.write(&dim.to_string());
24828                    }
24829                    // Map type back to SingleStore alias
24830                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
24831                        DataType::TinyInt { .. } => Some("I8"),
24832                        DataType::SmallInt { .. } => Some("I16"),
24833                        DataType::Int { .. } => Some("I32"),
24834                        DataType::BigInt { .. } => Some("I64"),
24835                        DataType::Float { .. } => Some("F32"),
24836                        DataType::Double { .. } => Some("F64"),
24837                        _ => None,
24838                    });
24839                    if let Some(alias) = type_alias {
24840                        if dimension.is_some() {
24841                            self.write(", ");
24842                        }
24843                        self.write(alias);
24844                    }
24845                    self.write(")");
24846                } else {
24847                    // Snowflake format: VECTOR(type, dimension)
24848                    self.write_keyword("VECTOR(");
24849                    if let Some(ref et) = element_type {
24850                        self.generate_data_type(et)?;
24851                        if dimension.is_some() {
24852                            self.write(", ");
24853                        }
24854                    }
24855                    if let Some(dim) = dimension {
24856                        self.write(&dim.to_string());
24857                    }
24858                    self.write(")");
24859                }
24860            }
24861            DataType::Object { fields, modifier } => {
24862                self.write_keyword("OBJECT(");
24863                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
24864                    if i > 0 {
24865                        self.write(", ");
24866                    }
24867                    self.write(name);
24868                    self.write(" ");
24869                    self.generate_data_type(dt)?;
24870                    if *not_null {
24871                        self.write_keyword(" NOT NULL");
24872                    }
24873                }
24874                self.write(")");
24875                if let Some(mod_str) = modifier {
24876                    self.write(" ");
24877                    self.write_keyword(mod_str);
24878                }
24879            }
24880            DataType::Struct { fields, nested } => {
24881                // Dialect-specific struct type mappings
24882                match self.config.dialect {
24883                    Some(DialectType::Snowflake) => {
24884                        // Snowflake maps STRUCT to OBJECT
24885                        self.write_keyword("OBJECT(");
24886                        for (i, field) in fields.iter().enumerate() {
24887                            if i > 0 {
24888                                self.write(", ");
24889                            }
24890                            if !field.name.is_empty() {
24891                                self.write(&field.name);
24892                                self.write(" ");
24893                            }
24894                            self.generate_data_type(&field.data_type)?;
24895                        }
24896                        self.write(")");
24897                    }
24898                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
24899                        // Presto/Trino use ROW(name TYPE, ...) syntax
24900                        self.write_keyword("ROW(");
24901                        for (i, field) in fields.iter().enumerate() {
24902                            if i > 0 {
24903                                self.write(", ");
24904                            }
24905                            if !field.name.is_empty() {
24906                                self.write(&field.name);
24907                                self.write(" ");
24908                            }
24909                            self.generate_data_type(&field.data_type)?;
24910                        }
24911                        self.write(")");
24912                    }
24913                    Some(DialectType::DuckDB) => {
24914                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
24915                        self.write_keyword("STRUCT(");
24916                        for (i, field) in fields.iter().enumerate() {
24917                            if i > 0 {
24918                                self.write(", ");
24919                            }
24920                            if !field.name.is_empty() {
24921                                self.write(&field.name);
24922                                self.write(" ");
24923                            }
24924                            self.generate_data_type(&field.data_type)?;
24925                        }
24926                        self.write(")");
24927                    }
24928                    Some(DialectType::ClickHouse) => {
24929                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
24930                        self.write("Tuple(");
24931                        for (i, field) in fields.iter().enumerate() {
24932                            if i > 0 {
24933                                self.write(", ");
24934                            }
24935                            if !field.name.is_empty() {
24936                                self.write(&field.name);
24937                                self.write(" ");
24938                            }
24939                            self.generate_data_type(&field.data_type)?;
24940                        }
24941                        self.write(")");
24942                    }
24943                    Some(DialectType::SingleStore) => {
24944                        // SingleStore uses RECORD(name TYPE, ...) for struct types
24945                        self.write_keyword("RECORD(");
24946                        for (i, field) in fields.iter().enumerate() {
24947                            if i > 0 {
24948                                self.write(", ");
24949                            }
24950                            if !field.name.is_empty() {
24951                                self.write(&field.name);
24952                                self.write(" ");
24953                            }
24954                            self.generate_data_type(&field.data_type)?;
24955                        }
24956                        self.write(")");
24957                    }
24958                    _ => {
24959                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
24960                        let force_angle_brackets = matches!(
24961                            self.config.dialect,
24962                            Some(DialectType::Hive)
24963                                | Some(DialectType::Spark)
24964                                | Some(DialectType::Databricks)
24965                        );
24966                        if *nested && !force_angle_brackets {
24967                            self.write_keyword("STRUCT(");
24968                            for (i, field) in fields.iter().enumerate() {
24969                                if i > 0 {
24970                                    self.write(", ");
24971                                }
24972                                if !field.name.is_empty() {
24973                                    self.write(&field.name);
24974                                    self.write(" ");
24975                                }
24976                                self.generate_data_type(&field.data_type)?;
24977                            }
24978                            self.write(")");
24979                        } else {
24980                            self.write_keyword("STRUCT<");
24981                            for (i, field) in fields.iter().enumerate() {
24982                                if i > 0 {
24983                                    self.write(", ");
24984                                }
24985                                if !field.name.is_empty() {
24986                                    // Named field: name TYPE (with configurable separator for Hive)
24987                                    self.write(&field.name);
24988                                    self.write(self.config.struct_field_sep);
24989                                }
24990                                // For anonymous fields, just output the type
24991                                self.generate_data_type(&field.data_type)?;
24992                                // Spark/Databricks: Output COMMENT clause if present
24993                                if let Some(comment) = &field.comment {
24994                                    self.write(" COMMENT '");
24995                                    self.write(comment);
24996                                    self.write("'");
24997                                }
24998                                // BigQuery: Output OPTIONS clause if present
24999                                if !field.options.is_empty() {
25000                                    self.write(" ");
25001                                    self.generate_options_clause(&field.options)?;
25002                                }
25003                            }
25004                            self.write(">");
25005                        }
25006                    }
25007                }
25008            }
25009            DataType::Enum {
25010                values,
25011                assignments,
25012            } => {
25013                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
25014                // ClickHouse: Enum('hello' = 1, 'world' = 2)
25015                if self.config.dialect == Some(DialectType::ClickHouse) {
25016                    self.write("Enum(");
25017                } else {
25018                    self.write_keyword("ENUM(");
25019                }
25020                for (i, val) in values.iter().enumerate() {
25021                    if i > 0 {
25022                        self.write(", ");
25023                    }
25024                    self.write("'");
25025                    self.write(val);
25026                    self.write("'");
25027                    if let Some(Some(assignment)) = assignments.get(i) {
25028                        self.write(" = ");
25029                        self.write(assignment);
25030                    }
25031                }
25032                self.write(")");
25033            }
25034            DataType::Set { values } => {
25035                // MySQL SET type: SET('a', 'b', 'c')
25036                self.write_keyword("SET(");
25037                for (i, val) in values.iter().enumerate() {
25038                    if i > 0 {
25039                        self.write(", ");
25040                    }
25041                    self.write("'");
25042                    self.write(val);
25043                    self.write("'");
25044                }
25045                self.write(")");
25046            }
25047            DataType::Union { fields } => {
25048                // DuckDB UNION type: UNION(num INT, str TEXT)
25049                self.write_keyword("UNION(");
25050                for (i, (name, dt)) in fields.iter().enumerate() {
25051                    if i > 0 {
25052                        self.write(", ");
25053                    }
25054                    if !name.is_empty() {
25055                        self.write(name);
25056                        self.write(" ");
25057                    }
25058                    self.generate_data_type(dt)?;
25059                }
25060                self.write(")");
25061            }
25062            DataType::Nullable { inner } => {
25063                // ClickHouse: Nullable(T), other dialects: just the inner type
25064                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25065                    self.write("Nullable(");
25066                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
25067                    let saved_depth = self.clickhouse_nullable_depth;
25068                    self.clickhouse_nullable_depth = -1;
25069                    self.generate_data_type(inner)?;
25070                    self.clickhouse_nullable_depth = saved_depth;
25071                    self.write(")");
25072                } else {
25073                    // Map ClickHouse-specific custom type names to standard types
25074                    match inner.as_ref() {
25075                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
25076                            self.generate_data_type(&DataType::Timestamp {
25077                                precision: None,
25078                                timezone: false,
25079                            })?;
25080                        }
25081                        _ => {
25082                            self.generate_data_type(inner)?;
25083                        }
25084                    }
25085                }
25086            }
25087            DataType::Custom { name } => {
25088                // Handle dialect-specific type transformations
25089                let name_upper = name.to_ascii_uppercase();
25090                match self.config.dialect {
25091                    Some(DialectType::ClickHouse) => {
25092                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
25093                            (name_upper[..idx].to_string(), &name[idx..])
25094                        } else {
25095                            (name_upper.clone(), "")
25096                        };
25097                        let mapped = match base_upper.as_str() {
25098                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
25099                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
25100                            "DATETIME64" => "DateTime64",
25101                            "DATE32" => "Date32",
25102                            "INT" => "Int32",
25103                            "MEDIUMINT" => "Int32",
25104                            "INT8" => "Int8",
25105                            "INT16" => "Int16",
25106                            "INT32" => "Int32",
25107                            "INT64" => "Int64",
25108                            "INT128" => "Int128",
25109                            "INT256" => "Int256",
25110                            "UINT8" => "UInt8",
25111                            "UINT16" => "UInt16",
25112                            "UINT32" => "UInt32",
25113                            "UINT64" => "UInt64",
25114                            "UINT128" => "UInt128",
25115                            "UINT256" => "UInt256",
25116                            "FLOAT32" => "Float32",
25117                            "FLOAT64" => "Float64",
25118                            "DECIMAL32" => "Decimal32",
25119                            "DECIMAL64" => "Decimal64",
25120                            "DECIMAL128" => "Decimal128",
25121                            "DECIMAL256" => "Decimal256",
25122                            "ENUM" => "Enum",
25123                            "ENUM8" => "Enum8",
25124                            "ENUM16" => "Enum16",
25125                            "FIXEDSTRING" => "FixedString",
25126                            "NESTED" => "Nested",
25127                            "LOWCARDINALITY" => "LowCardinality",
25128                            "NULLABLE" => "Nullable",
25129                            "IPV4" => "IPv4",
25130                            "IPV6" => "IPv6",
25131                            "POINT" => "Point",
25132                            "RING" => "Ring",
25133                            "LINESTRING" => "LineString",
25134                            "MULTILINESTRING" => "MultiLineString",
25135                            "POLYGON" => "Polygon",
25136                            "MULTIPOLYGON" => "MultiPolygon",
25137                            "AGGREGATEFUNCTION" => "AggregateFunction",
25138                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
25139                            "DYNAMIC" => "Dynamic",
25140                            _ => "",
25141                        };
25142                        if mapped.is_empty() {
25143                            self.write(name);
25144                        } else {
25145                            self.write(mapped);
25146                            if matches!(base_upper.as_str(), "ENUM8" | "ENUM16")
25147                                && !suffix.is_empty()
25148                            {
25149                                let escaped_suffix = suffix
25150                                    .replace('\\', "\\\\")
25151                                    .replace('\t', "\\t")
25152                                    .replace('\n', "\\n")
25153                                    .replace('\r', "\\r");
25154                                self.write(&escaped_suffix);
25155                            } else {
25156                                self.write(suffix);
25157                            }
25158                        }
25159                    }
25160                    Some(DialectType::MySQL)
25161                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
25162                    {
25163                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
25164                        self.write_keyword("TIMESTAMP");
25165                    }
25166                    Some(DialectType::Snowflake) => {
25167                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
25168                            (name_upper[..idx].to_string(), &name[idx..])
25169                        } else {
25170                            (name_upper.clone(), "")
25171                        };
25172
25173                        match base_upper.as_str() {
25174                            "TIMESTAMPNTZ" | "TIMESTAMP_NTZ" => {
25175                                self.write_keyword("TIMESTAMPNTZ");
25176                                self.write(suffix);
25177                            }
25178                            "TIMESTAMPLTZ" | "TIMESTAMP_LTZ" => {
25179                                self.write_keyword("TIMESTAMPLTZ");
25180                                self.write(suffix);
25181                            }
25182                            "TIMESTAMPTZ" | "TIMESTAMP_TZ" => {
25183                                self.write_keyword("TIMESTAMPTZ");
25184                                self.write(suffix);
25185                            }
25186                            _ => self.write(name),
25187                        }
25188                    }
25189                    Some(DialectType::Fabric) => {
25190                        let (base_upper, args_str) = if let Some(idx) = name.find('(') {
25191                            (name_upper[..idx].to_string(), Some(&name[idx..]))
25192                        } else {
25193                            (name_upper.clone(), None)
25194                        };
25195
25196                        match base_upper.as_str() {
25197                            "NVARCHAR" => {
25198                                self.write_keyword("VARCHAR");
25199                                if let Some(args) = args_str {
25200                                    self.write(args);
25201                                }
25202                            }
25203                            "NCHAR" => {
25204                                self.write_keyword("CHAR");
25205                                if let Some(args) = args_str {
25206                                    self.write(args);
25207                                }
25208                            }
25209                            _ => self.write(name),
25210                        }
25211                    }
25212                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
25213                        self.write_keyword("SQL_VARIANT");
25214                    }
25215                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
25216                        self.write_keyword("DECIMAL(38, 5)");
25217                    }
25218                    Some(DialectType::Exasol) => {
25219                        // Exasol type mappings for custom types
25220                        match name_upper.as_str() {
25221                            // Binary types → VARCHAR
25222                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
25223                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
25224                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
25225                            // Integer types
25226                            "MEDIUMINT" => self.write_keyword("INT"),
25227                            // Decimal types → DECIMAL
25228                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
25229                                self.write_keyword("DECIMAL")
25230                            }
25231                            // Timestamp types
25232                            "DATETIME" => self.write_keyword("TIMESTAMP"),
25233                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
25234                            _ => self.write(name),
25235                        }
25236                    }
25237                    Some(DialectType::Dremio) => {
25238                        // Dremio type mappings for custom types
25239                        match name_upper.as_str() {
25240                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
25241                            "ARRAY" => self.write_keyword("LIST"),
25242                            "NCHAR" => self.write_keyword("VARCHAR"),
25243                            _ => self.write(name),
25244                        }
25245                    }
25246                    // Map dialect-specific custom types to standard SQL types for other dialects
25247                    _ => {
25248                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
25249                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
25250                            (name_upper[..idx].to_string(), Some(&name[idx..]))
25251                        } else {
25252                            (name_upper.clone(), None)
25253                        };
25254
25255                        match base_upper.as_str() {
25256                            "INT64"
25257                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25258                            {
25259                                self.write_keyword("BIGINT");
25260                            }
25261                            "FLOAT64"
25262                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25263                            {
25264                                self.write_keyword("DOUBLE");
25265                            }
25266                            "BOOL"
25267                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25268                            {
25269                                self.write_keyword("BOOLEAN");
25270                            }
25271                            "BYTES"
25272                                if matches!(
25273                                    self.config.dialect,
25274                                    Some(DialectType::Spark)
25275                                        | Some(DialectType::Hive)
25276                                        | Some(DialectType::Databricks)
25277                                ) =>
25278                            {
25279                                self.write_keyword("BINARY");
25280                            }
25281                            "BYTES"
25282                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25283                            {
25284                                self.write_keyword("VARBINARY");
25285                            }
25286                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
25287                            "DATETIME2" | "SMALLDATETIME"
25288                                if !matches!(
25289                                    self.config.dialect,
25290                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25291                                ) =>
25292                            {
25293                                // PostgreSQL preserves precision, others don't
25294                                if matches!(
25295                                    self.config.dialect,
25296                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
25297                                ) {
25298                                    self.write_keyword("TIMESTAMP");
25299                                    if let Some(args) = _args_str {
25300                                        self.write(args);
25301                                    }
25302                                } else {
25303                                    self.write_keyword("TIMESTAMP");
25304                                }
25305                            }
25306                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
25307                            "DATETIMEOFFSET"
25308                                if !matches!(
25309                                    self.config.dialect,
25310                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25311                                ) =>
25312                            {
25313                                if matches!(
25314                                    self.config.dialect,
25315                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
25316                                ) {
25317                                    self.write_keyword("TIMESTAMPTZ");
25318                                    if let Some(args) = _args_str {
25319                                        self.write(args);
25320                                    }
25321                                } else {
25322                                    self.write_keyword("TIMESTAMPTZ");
25323                                }
25324                            }
25325                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
25326                            "UNIQUEIDENTIFIER"
25327                                if !matches!(
25328                                    self.config.dialect,
25329                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25330                                ) =>
25331                            {
25332                                match self.config.dialect {
25333                                    Some(DialectType::Spark)
25334                                    | Some(DialectType::Databricks)
25335                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
25336                                    _ => self.write_keyword("UUID"),
25337                                }
25338                            }
25339                            // TSQL BIT -> BOOLEAN for most dialects
25340                            "BIT"
25341                                if !matches!(
25342                                    self.config.dialect,
25343                                    Some(DialectType::TSQL)
25344                                        | Some(DialectType::Fabric)
25345                                        | Some(DialectType::PostgreSQL)
25346                                        | Some(DialectType::MySQL)
25347                                        | Some(DialectType::DuckDB)
25348                                ) =>
25349                            {
25350                                self.write_keyword("BOOLEAN");
25351                            }
25352                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
25353                            "NVARCHAR"
25354                                if !matches!(
25355                                    self.config.dialect,
25356                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25357                                ) =>
25358                            {
25359                                match self.config.dialect {
25360                                    Some(DialectType::Oracle) => {
25361                                        // Oracle: NVARCHAR -> NVARCHAR2
25362                                        self.write_keyword("NVARCHAR2");
25363                                        if let Some(args) = _args_str {
25364                                            self.write(args);
25365                                        }
25366                                    }
25367                                    Some(DialectType::BigQuery) => {
25368                                        // BigQuery: NVARCHAR -> STRING
25369                                        self.write_keyword("STRING");
25370                                    }
25371                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
25372                                        self.write_keyword("TEXT");
25373                                        if let Some(args) = _args_str {
25374                                            self.write(args);
25375                                        }
25376                                    }
25377                                    Some(DialectType::Hive) => {
25378                                        // Hive: NVARCHAR -> STRING
25379                                        self.write_keyword("STRING");
25380                                    }
25381                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
25382                                        if _args_str.is_some() {
25383                                            self.write_keyword("VARCHAR");
25384                                            self.write(_args_str.unwrap());
25385                                        } else {
25386                                            self.write_keyword("STRING");
25387                                        }
25388                                    }
25389                                    _ => {
25390                                        self.write_keyword("VARCHAR");
25391                                        if let Some(args) = _args_str {
25392                                            self.write(args);
25393                                        }
25394                                    }
25395                                }
25396                            }
25397                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
25398                            "NCHAR"
25399                                if !matches!(
25400                                    self.config.dialect,
25401                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25402                                ) =>
25403                            {
25404                                match self.config.dialect {
25405                                    Some(DialectType::Oracle) => {
25406                                        // Oracle natively supports NCHAR
25407                                        self.write_keyword("NCHAR");
25408                                        if let Some(args) = _args_str {
25409                                            self.write(args);
25410                                        }
25411                                    }
25412                                    Some(DialectType::BigQuery) => {
25413                                        // BigQuery: NCHAR -> STRING
25414                                        self.write_keyword("STRING");
25415                                    }
25416                                    Some(DialectType::Hive) => {
25417                                        // Hive: NCHAR -> STRING
25418                                        self.write_keyword("STRING");
25419                                    }
25420                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
25421                                        self.write_keyword("TEXT");
25422                                        if let Some(args) = _args_str {
25423                                            self.write(args);
25424                                        }
25425                                    }
25426                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
25427                                        if _args_str.is_some() {
25428                                            self.write_keyword("CHAR");
25429                                            self.write(_args_str.unwrap());
25430                                        } else {
25431                                            self.write_keyword("STRING");
25432                                        }
25433                                    }
25434                                    _ => {
25435                                        self.write_keyword("CHAR");
25436                                        if let Some(args) = _args_str {
25437                                            self.write(args);
25438                                        }
25439                                    }
25440                                }
25441                            }
25442                            // MySQL text variant types -> map to appropriate target type
25443                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
25444                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
25445                                Some(DialectType::MySQL)
25446                                | Some(DialectType::SingleStore)
25447                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
25448                                Some(DialectType::Spark)
25449                                | Some(DialectType::Databricks)
25450                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
25451                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
25452                                Some(DialectType::Presto)
25453                                | Some(DialectType::Trino)
25454                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
25455                                Some(DialectType::Snowflake)
25456                                | Some(DialectType::Redshift)
25457                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
25458                                _ => self.write_keyword("TEXT"),
25459                            },
25460                            // MySQL blob variant types -> map to appropriate target type
25461                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
25462                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
25463                                Some(DialectType::MySQL)
25464                                | Some(DialectType::SingleStore)
25465                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
25466                                Some(DialectType::Spark)
25467                                | Some(DialectType::Databricks)
25468                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
25469                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
25470                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
25471                                Some(DialectType::Presto)
25472                                | Some(DialectType::Trino)
25473                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
25474                                Some(DialectType::Snowflake)
25475                                | Some(DialectType::Redshift)
25476                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
25477                                _ => self.write_keyword("BLOB"),
25478                            },
25479                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
25480                            "LONGVARCHAR" => match self.config.dialect {
25481                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
25482                                _ => self.write_keyword("VARCHAR"),
25483                            },
25484                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
25485                            "DATETIME" => {
25486                                match self.config.dialect {
25487                                    Some(DialectType::MySQL)
25488                                    | Some(DialectType::Doris)
25489                                    | Some(DialectType::StarRocks)
25490                                    | Some(DialectType::TSQL)
25491                                    | Some(DialectType::Fabric)
25492                                    | Some(DialectType::BigQuery)
25493                                    | Some(DialectType::SQLite)
25494                                    | Some(DialectType::Snowflake) => {
25495                                        self.write_keyword("DATETIME");
25496                                        if let Some(args) = _args_str {
25497                                            self.write(args);
25498                                        }
25499                                    }
25500                                    Some(_) => {
25501                                        // Only map to TIMESTAMP when we have a specific target dialect
25502                                        self.write_keyword("TIMESTAMP");
25503                                        if let Some(args) = _args_str {
25504                                            self.write(args);
25505                                        }
25506                                    }
25507                                    None => {
25508                                        // No dialect - preserve original
25509                                        self.write(name);
25510                                    }
25511                                }
25512                            }
25513                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
25514                            "VARCHAR2"
25515                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
25516                            {
25517                                match self.config.dialect {
25518                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
25519                                        self.write_keyword("TEXT");
25520                                    }
25521                                    Some(DialectType::Hive)
25522                                    | Some(DialectType::Spark)
25523                                    | Some(DialectType::Databricks)
25524                                    | Some(DialectType::BigQuery)
25525                                    | Some(DialectType::ClickHouse)
25526                                    | Some(DialectType::StarRocks)
25527                                    | Some(DialectType::Doris) => {
25528                                        self.write_keyword("STRING");
25529                                    }
25530                                    _ => {
25531                                        self.write_keyword("VARCHAR");
25532                                        if let Some(args) = _args_str {
25533                                            self.write(args);
25534                                        }
25535                                    }
25536                                }
25537                            }
25538                            "NVARCHAR2"
25539                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
25540                            {
25541                                match self.config.dialect {
25542                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
25543                                        self.write_keyword("TEXT");
25544                                    }
25545                                    Some(DialectType::Hive)
25546                                    | Some(DialectType::Spark)
25547                                    | Some(DialectType::Databricks)
25548                                    | Some(DialectType::BigQuery)
25549                                    | Some(DialectType::ClickHouse)
25550                                    | Some(DialectType::StarRocks)
25551                                    | Some(DialectType::Doris) => {
25552                                        self.write_keyword("STRING");
25553                                    }
25554                                    _ => {
25555                                        self.write_keyword("VARCHAR");
25556                                        if let Some(args) = _args_str {
25557                                            self.write(args);
25558                                        }
25559                                    }
25560                                }
25561                            }
25562                            _ => self.write(name),
25563                        }
25564                    }
25565                }
25566            }
25567            DataType::Geometry { subtype, srid } => {
25568                // Dialect-specific geometry type mappings
25569                match self.config.dialect {
25570                    Some(DialectType::MySQL) => {
25571                        // MySQL uses POINT SRID 4326 syntax for specific types
25572                        if let Some(sub) = subtype {
25573                            self.write_keyword(sub);
25574                            if let Some(s) = srid {
25575                                self.write(" SRID ");
25576                                self.write(&s.to_string());
25577                            }
25578                        } else {
25579                            self.write_keyword("GEOMETRY");
25580                        }
25581                    }
25582                    Some(DialectType::BigQuery) => {
25583                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
25584                        self.write_keyword("GEOGRAPHY");
25585                    }
25586                    Some(DialectType::Teradata) => {
25587                        // Teradata uses ST_GEOMETRY
25588                        self.write_keyword("ST_GEOMETRY");
25589                        if subtype.is_some() || srid.is_some() {
25590                            self.write("(");
25591                            if let Some(sub) = subtype {
25592                                self.write_keyword(sub);
25593                            }
25594                            if let Some(s) = srid {
25595                                if subtype.is_some() {
25596                                    self.write(", ");
25597                                }
25598                                self.write(&s.to_string());
25599                            }
25600                            self.write(")");
25601                        }
25602                    }
25603                    _ => {
25604                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
25605                        self.write_keyword("GEOMETRY");
25606                        if subtype.is_some() || srid.is_some() {
25607                            self.write("(");
25608                            if let Some(sub) = subtype {
25609                                self.write_keyword(sub);
25610                            }
25611                            if let Some(s) = srid {
25612                                if subtype.is_some() {
25613                                    self.write(", ");
25614                                }
25615                                self.write(&s.to_string());
25616                            }
25617                            self.write(")");
25618                        }
25619                    }
25620                }
25621            }
25622            DataType::Geography { subtype, srid } => {
25623                // Dialect-specific geography type mappings
25624                match self.config.dialect {
25625                    Some(DialectType::MySQL) => {
25626                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
25627                        if let Some(sub) = subtype {
25628                            self.write_keyword(sub);
25629                        } else {
25630                            self.write_keyword("GEOMETRY");
25631                        }
25632                        // Geography implies SRID 4326 (WGS84)
25633                        let effective_srid = srid.unwrap_or(4326);
25634                        self.write(" SRID ");
25635                        self.write(&effective_srid.to_string());
25636                    }
25637                    Some(DialectType::BigQuery) => {
25638                        // BigQuery uses simple GEOGRAPHY without parameters
25639                        self.write_keyword("GEOGRAPHY");
25640                    }
25641                    Some(DialectType::Snowflake) => {
25642                        // Snowflake uses GEOGRAPHY without parameters
25643                        self.write_keyword("GEOGRAPHY");
25644                    }
25645                    _ => {
25646                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
25647                        self.write_keyword("GEOGRAPHY");
25648                        if subtype.is_some() || srid.is_some() {
25649                            self.write("(");
25650                            if let Some(sub) = subtype {
25651                                self.write_keyword(sub);
25652                            }
25653                            if let Some(s) = srid {
25654                                if subtype.is_some() {
25655                                    self.write(", ");
25656                                }
25657                                self.write(&s.to_string());
25658                            }
25659                            self.write(")");
25660                        }
25661                    }
25662                }
25663            }
25664            DataType::CharacterSet { name } => {
25665                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
25666                self.write_keyword("CHAR CHARACTER SET ");
25667                self.write(name);
25668            }
25669            _ => self.write("UNKNOWN"),
25670        }
25671        Ok(())
25672    }
25673
25674    // === Helper methods ===
25675
25676    #[inline]
25677    fn write(&mut self, s: &str) {
25678        self.output.push_str(s);
25679    }
25680
25681    #[inline]
25682    fn write_space(&mut self) {
25683        self.output.push(' ');
25684    }
25685
25686    #[inline]
25687    fn write_keyword(&mut self, keyword: &str) {
25688        if self.config.uppercase_keywords {
25689            self.output.push_str(keyword);
25690        } else {
25691            for b in keyword.bytes() {
25692                self.output.push(b.to_ascii_lowercase() as char);
25693            }
25694        }
25695    }
25696
25697    /// Write a function name respecting the normalize_functions config setting
25698    fn write_func_name(&mut self, name: &str) {
25699        let normalized = self.normalize_func_name(name);
25700        self.output.push_str(normalized.as_ref());
25701    }
25702
25703    /// Convert strptime format string to Exasol format string
25704    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
25705    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
25706    fn convert_strptime_to_exasol_format(format: &str) -> String {
25707        let mut result = String::new();
25708        let chars: Vec<char> = format.chars().collect();
25709        let mut i = 0;
25710        while i < chars.len() {
25711            if chars[i] == '%' && i + 1 < chars.len() {
25712                let spec = chars[i + 1];
25713                let exasol_spec = match spec {
25714                    'Y' => "YYYY",
25715                    'y' => "YY",
25716                    'm' => "MM",
25717                    'd' => "DD",
25718                    'H' => "HH",
25719                    'M' => "MI",
25720                    'S' => "SS",
25721                    'a' => "DY",    // abbreviated weekday name
25722                    'A' => "DAY",   // full weekday name
25723                    'b' => "MON",   // abbreviated month name
25724                    'B' => "MONTH", // full month name
25725                    'I' => "H12",   // 12-hour format
25726                    'u' => "ID",    // ISO weekday (1-7)
25727                    'V' => "IW",    // ISO week number
25728                    'G' => "IYYY",  // ISO year
25729                    'W' => "UW",    // Week number (Monday as first day)
25730                    'U' => "UW",    // Week number (Sunday as first day)
25731                    'z' => "Z",     // timezone offset
25732                    _ => {
25733                        // Unknown specifier, keep as-is
25734                        result.push('%');
25735                        result.push(spec);
25736                        i += 2;
25737                        continue;
25738                    }
25739                };
25740                result.push_str(exasol_spec);
25741                i += 2;
25742            } else {
25743                result.push(chars[i]);
25744                i += 1;
25745            }
25746        }
25747        result
25748    }
25749
25750    /// Convert strptime format string to PostgreSQL/Redshift format string
25751    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
25752    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
25753    fn convert_strptime_to_postgres_format(format: &str) -> String {
25754        let mut result = String::new();
25755        let chars: Vec<char> = format.chars().collect();
25756        let mut i = 0;
25757        while i < chars.len() {
25758            if chars[i] == '%' && i + 1 < chars.len() {
25759                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
25760                if chars[i + 1] == '-' && i + 2 < chars.len() {
25761                    let spec = chars[i + 2];
25762                    let pg_spec = match spec {
25763                        'd' => "FMDD",
25764                        'm' => "FMMM",
25765                        'H' => "FMHH24",
25766                        'M' => "FMMI",
25767                        'S' => "FMSS",
25768                        _ => {
25769                            result.push('%');
25770                            result.push('-');
25771                            result.push(spec);
25772                            i += 3;
25773                            continue;
25774                        }
25775                    };
25776                    result.push_str(pg_spec);
25777                    i += 3;
25778                    continue;
25779                }
25780                let spec = chars[i + 1];
25781                let pg_spec = match spec {
25782                    'Y' => "YYYY",
25783                    'y' => "YY",
25784                    'm' => "MM",
25785                    'd' => "DD",
25786                    'H' => "HH24",
25787                    'I' => "HH12",
25788                    'M' => "MI",
25789                    'S' => "SS",
25790                    'f' => "US",      // microseconds
25791                    'u' => "D",       // day of week (1=Monday)
25792                    'j' => "DDD",     // day of year
25793                    'z' => "OF",      // UTC offset
25794                    'Z' => "TZ",      // timezone name
25795                    'A' => "TMDay",   // full weekday name
25796                    'a' => "TMDy",    // abbreviated weekday name
25797                    'b' => "TMMon",   // abbreviated month name
25798                    'B' => "TMMonth", // full month name
25799                    'U' => "WW",      // week number
25800                    _ => {
25801                        // Unknown specifier, keep as-is
25802                        result.push('%');
25803                        result.push(spec);
25804                        i += 2;
25805                        continue;
25806                    }
25807                };
25808                result.push_str(pg_spec);
25809                i += 2;
25810            } else {
25811                result.push(chars[i]);
25812                i += 1;
25813            }
25814        }
25815        result
25816    }
25817
25818    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
25819    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
25820        if self.config.limit_only_literals {
25821            if let Some(value) = Self::try_evaluate_constant(expr) {
25822                self.write(&value.to_string());
25823                return Ok(());
25824            }
25825        }
25826        self.generate_expression(expr)
25827    }
25828
25829    /// Format a comment with proper spacing.
25830    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
25831    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
25832    fn write_formatted_comment(&mut self, comment: &str) {
25833        // Normalize all comments to block comment format /* ... */
25834        // This matches Python sqlglot behavior which always outputs block comments
25835        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
25836            // Already block comment - extract inner content
25837            // Preserve internal whitespace, but ensure at least one space padding
25838            &comment[2..comment.len() - 2]
25839        } else if comment.starts_with("--") {
25840            // Line comment - extract content after --
25841            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
25842            &comment[2..]
25843        } else {
25844            // Raw content (no delimiters)
25845            comment
25846        };
25847        // Skip empty comments (e.g., bare "--" with no content)
25848        if content.trim().is_empty() {
25849            return;
25850        }
25851        // Escape nested block comment markers to prevent premature closure or unintended nesting.
25852        // This matches Python sqlglot's sanitize_comment behavior.
25853        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
25854        let content = &sanitized;
25855        // Ensure at least one space after /* and before */
25856        self.output.push_str("/*");
25857        if !content.starts_with(' ') {
25858            self.output.push(' ');
25859        }
25860        self.output.push_str(content);
25861        if !content.ends_with(' ') {
25862            self.output.push(' ');
25863        }
25864        self.output.push_str("*/");
25865    }
25866
25867    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
25868    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
25869    fn escape_block_for_single_quote(&self, block: &str) -> String {
25870        let escape_backslash = matches!(
25871            self.config.dialect,
25872            Some(crate::dialects::DialectType::Snowflake)
25873        );
25874        let mut escaped = String::with_capacity(block.len() + 4);
25875        for ch in block.chars() {
25876            if ch == '\'' {
25877                escaped.push('\\');
25878                escaped.push('\'');
25879            } else if escape_backslash && ch == '\\' {
25880                escaped.push('\\');
25881                escaped.push('\\');
25882            } else {
25883                escaped.push(ch);
25884            }
25885        }
25886        escaped
25887    }
25888
25889    fn write_newline(&mut self) {
25890        self.output.push('\n');
25891    }
25892
25893    fn write_indent(&mut self) {
25894        for _ in 0..self.indent_level {
25895            self.output.push_str(self.config.indent);
25896        }
25897    }
25898
25899    // === SQLGlot-style pretty printing helpers ===
25900
25901    /// Returns the separator string for pretty printing.
25902    /// Check if the total length of arguments exceeds max_text_width.
25903    /// Used for dynamic line breaking in expressions() formatting.
25904    fn too_wide(&self, args: &[String]) -> bool {
25905        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
25906    }
25907
25908    /// Generate an expression to a string using a temporary non-pretty generator.
25909    /// Useful for width calculations before deciding on formatting.
25910    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
25911        let config = GeneratorConfig {
25912            pretty: false,
25913            dialect: self.config.dialect,
25914            ..Default::default()
25915        };
25916        let mut gen = Generator::with_config(config);
25917        gen.generate_expression(expr)?;
25918        Ok(gen.output)
25919    }
25920
25921    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
25922    /// In pretty mode: newline + indented keyword + newline + indented condition
25923    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
25924        if self.config.pretty {
25925            self.write_newline();
25926            self.write_indent();
25927            self.write_keyword(keyword);
25928            self.write_newline();
25929            self.indent_level += 1;
25930            self.write_indent();
25931            self.generate_expression(condition)?;
25932            self.indent_level -= 1;
25933        } else {
25934            self.write_space();
25935            self.write_keyword(keyword);
25936            self.write_space();
25937            self.generate_expression(condition)?;
25938        }
25939        Ok(())
25940    }
25941
25942    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
25943    /// In pretty mode: each expression on new line with indentation
25944    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
25945        if exprs.is_empty() {
25946            return Ok(());
25947        }
25948
25949        if self.config.pretty {
25950            self.write_newline();
25951            self.write_indent();
25952            self.write_keyword(keyword);
25953            self.write_newline();
25954            self.indent_level += 1;
25955            for (i, expr) in exprs.iter().enumerate() {
25956                if i > 0 {
25957                    self.write(",");
25958                    self.write_newline();
25959                }
25960                self.write_indent();
25961                self.generate_expression(expr)?;
25962            }
25963            self.indent_level -= 1;
25964        } else {
25965            self.write_space();
25966            self.write_keyword(keyword);
25967            self.write_space();
25968            for (i, expr) in exprs.iter().enumerate() {
25969                if i > 0 {
25970                    self.write(", ");
25971                }
25972                self.generate_expression(expr)?;
25973            }
25974        }
25975        Ok(())
25976    }
25977
25978    /// Writes ORDER BY / SORT BY clause with Ordered expressions
25979    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
25980        if orderings.is_empty() {
25981            return Ok(());
25982        }
25983
25984        if self.config.pretty {
25985            self.write_newline();
25986            self.write_indent();
25987            self.write_keyword(keyword);
25988            self.write_newline();
25989            self.indent_level += 1;
25990            for (i, ordered) in orderings.iter().enumerate() {
25991                if i > 0 {
25992                    self.write(",");
25993                    self.write_newline();
25994                }
25995                self.write_indent();
25996                self.generate_ordered(ordered)?;
25997            }
25998            self.indent_level -= 1;
25999        } else {
26000            self.write_space();
26001            self.write_keyword(keyword);
26002            self.write_space();
26003            for (i, ordered) in orderings.iter().enumerate() {
26004                if i > 0 {
26005                    self.write(", ");
26006                }
26007                self.generate_ordered(ordered)?;
26008            }
26009        }
26010        Ok(())
26011    }
26012
26013    /// Writes WINDOW clause with named window definitions
26014    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
26015        if windows.is_empty() {
26016            return Ok(());
26017        }
26018
26019        if self.config.pretty {
26020            self.write_newline();
26021            self.write_indent();
26022            self.write_keyword("WINDOW");
26023            self.write_newline();
26024            self.indent_level += 1;
26025            for (i, named_window) in windows.iter().enumerate() {
26026                if i > 0 {
26027                    self.write(",");
26028                    self.write_newline();
26029                }
26030                self.write_indent();
26031                self.generate_identifier(&named_window.name)?;
26032                self.write_space();
26033                self.write_keyword("AS");
26034                self.write(" (");
26035                self.generate_over(&named_window.spec)?;
26036                self.write(")");
26037            }
26038            self.indent_level -= 1;
26039        } else {
26040            self.write_space();
26041            self.write_keyword("WINDOW");
26042            self.write_space();
26043            for (i, named_window) in windows.iter().enumerate() {
26044                if i > 0 {
26045                    self.write(", ");
26046                }
26047                self.generate_identifier(&named_window.name)?;
26048                self.write_space();
26049                self.write_keyword("AS");
26050                self.write(" (");
26051                self.generate_over(&named_window.spec)?;
26052                self.write(")");
26053            }
26054        }
26055        Ok(())
26056    }
26057
26058    // === BATCH-GENERATED STUB METHODS (481 variants) ===
26059    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
26060        // AI_AGG(this, expression)
26061        self.write_keyword("AI_AGG");
26062        self.write("(");
26063        self.generate_expression(&e.this)?;
26064        self.write(", ");
26065        self.generate_expression(&e.expression)?;
26066        self.write(")");
26067        Ok(())
26068    }
26069
26070    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
26071        // AI_CLASSIFY(input, [categories], [config])
26072        self.write_keyword("AI_CLASSIFY");
26073        self.write("(");
26074        self.generate_expression(&e.this)?;
26075        if let Some(categories) = &e.categories {
26076            self.write(", ");
26077            self.generate_expression(categories)?;
26078        }
26079        if let Some(config) = &e.config {
26080            self.write(", ");
26081            self.generate_expression(config)?;
26082        }
26083        self.write(")");
26084        Ok(())
26085    }
26086
26087    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
26088        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
26089        self.write_keyword("ADD");
26090        self.write_space();
26091        if e.exists {
26092            self.write_keyword("IF NOT EXISTS");
26093            self.write_space();
26094        }
26095        self.generate_expression(&e.this)?;
26096        if let Some(location) = &e.location {
26097            self.write_space();
26098            self.generate_expression(location)?;
26099        }
26100        Ok(())
26101    }
26102
26103    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
26104        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
26105        self.write_keyword("ALGORITHM");
26106        self.write("=");
26107        self.generate_expression(&e.this)?;
26108        Ok(())
26109    }
26110
26111    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
26112        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
26113        self.generate_expression(&e.this)?;
26114        self.write_space();
26115        self.write_keyword("AS");
26116        self.write(" (");
26117        for (i, expr) in e.expressions.iter().enumerate() {
26118            if i > 0 {
26119                self.write(", ");
26120            }
26121            self.generate_expression(expr)?;
26122        }
26123        self.write(")");
26124        Ok(())
26125    }
26126
26127    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
26128        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
26129        self.write_keyword("ALLOWED_VALUES");
26130        self.write_space();
26131        for (i, expr) in e.expressions.iter().enumerate() {
26132            if i > 0 {
26133                self.write(", ");
26134            }
26135            self.generate_expression(expr)?;
26136        }
26137        Ok(())
26138    }
26139
26140    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
26141        // Python: complex logic based on dtype, default, comment, visible, etc.
26142        self.write_keyword("ALTER COLUMN");
26143        self.write_space();
26144        self.generate_expression(&e.this)?;
26145
26146        if let Some(dtype) = &e.dtype {
26147            self.write_space();
26148            self.write_keyword("SET DATA TYPE");
26149            self.write_space();
26150            self.generate_expression(dtype)?;
26151            if let Some(collate) = &e.collate {
26152                self.write_space();
26153                self.write_keyword("COLLATE");
26154                self.write_space();
26155                self.generate_expression(collate)?;
26156            }
26157            if let Some(using) = &e.using {
26158                self.write_space();
26159                self.write_keyword("USING");
26160                self.write_space();
26161                self.generate_expression(using)?;
26162            }
26163        } else if let Some(default) = &e.default {
26164            self.write_space();
26165            self.write_keyword("SET DEFAULT");
26166            self.write_space();
26167            self.generate_expression(default)?;
26168        } else if let Some(comment) = &e.comment {
26169            self.write_space();
26170            self.write_keyword("COMMENT");
26171            self.write_space();
26172            self.generate_expression(comment)?;
26173        } else if let Some(drop) = &e.drop {
26174            self.write_space();
26175            self.write_keyword("DROP");
26176            self.write_space();
26177            self.generate_expression(drop)?;
26178        } else if let Some(visible) = &e.visible {
26179            self.write_space();
26180            self.generate_expression(visible)?;
26181        } else if let Some(rename_to) = &e.rename_to {
26182            self.write_space();
26183            self.write_keyword("RENAME TO");
26184            self.write_space();
26185            self.generate_expression(rename_to)?;
26186        } else if let Some(allow_null) = &e.allow_null {
26187            self.write_space();
26188            self.generate_expression(allow_null)?;
26189        }
26190        Ok(())
26191    }
26192
26193    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
26194        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
26195        self.write_keyword("ALTER SESSION");
26196        self.write_space();
26197        if e.unset.is_some() {
26198            self.write_keyword("UNSET");
26199        } else {
26200            self.write_keyword("SET");
26201        }
26202        self.write_space();
26203        for (i, expr) in e.expressions.iter().enumerate() {
26204            if i > 0 {
26205                self.write(", ");
26206            }
26207            self.generate_expression(expr)?;
26208        }
26209        Ok(())
26210    }
26211
26212    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
26213        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
26214        self.write_keyword("SET");
26215
26216        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
26217        if let Some(opt) = &e.option {
26218            self.write_space();
26219            self.generate_expression(opt)?;
26220        }
26221
26222        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
26223        // Check if expressions look like property assignments
26224        if !e.expressions.is_empty() {
26225            // Check if this looks like property assignments (for SET PROPERTIES)
26226            let is_properties = e
26227                .expressions
26228                .iter()
26229                .any(|expr| matches!(expr, Expression::Eq(_)));
26230            if is_properties && e.option.is_none() {
26231                self.write_space();
26232                self.write_keyword("PROPERTIES");
26233            }
26234            self.write_space();
26235            for (i, expr) in e.expressions.iter().enumerate() {
26236                if i > 0 {
26237                    self.write(", ");
26238                }
26239                self.generate_expression(expr)?;
26240            }
26241        }
26242
26243        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
26244        if let Some(file_format) = &e.file_format {
26245            self.write(" ");
26246            self.write_keyword("STAGE_FILE_FORMAT");
26247            self.write(" = (");
26248            self.generate_space_separated_properties(file_format)?;
26249            self.write(")");
26250        }
26251
26252        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
26253        if let Some(copy_options) = &e.copy_options {
26254            self.write(" ");
26255            self.write_keyword("STAGE_COPY_OPTIONS");
26256            self.write(" = (");
26257            self.generate_space_separated_properties(copy_options)?;
26258            self.write(")");
26259        }
26260
26261        // Generate TAG ...
26262        if let Some(tag) = &e.tag {
26263            self.write(" ");
26264            self.write_keyword("TAG");
26265            self.write(" ");
26266            self.generate_expression(tag)?;
26267        }
26268
26269        Ok(())
26270    }
26271
26272    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
26273    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
26274        match expr {
26275            Expression::Tuple(t) => {
26276                for (i, prop) in t.expressions.iter().enumerate() {
26277                    if i > 0 {
26278                        self.write(" ");
26279                    }
26280                    self.generate_expression(prop)?;
26281                }
26282            }
26283            _ => {
26284                self.generate_expression(expr)?;
26285            }
26286        }
26287        Ok(())
26288    }
26289
26290    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
26291        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
26292        self.write_keyword("ALTER");
26293        if e.compound.is_some() {
26294            self.write_space();
26295            self.write_keyword("COMPOUND");
26296        }
26297        self.write_space();
26298        self.write_keyword("SORTKEY");
26299        self.write_space();
26300        if let Some(this) = &e.this {
26301            self.generate_expression(this)?;
26302        } else if !e.expressions.is_empty() {
26303            self.write("(");
26304            for (i, expr) in e.expressions.iter().enumerate() {
26305                if i > 0 {
26306                    self.write(", ");
26307                }
26308                self.generate_expression(expr)?;
26309            }
26310            self.write(")");
26311        }
26312        Ok(())
26313    }
26314
26315    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
26316        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
26317        self.write_keyword("ANALYZE");
26318        if !e.options.is_empty() {
26319            self.write_space();
26320            for (i, opt) in e.options.iter().enumerate() {
26321                if i > 0 {
26322                    self.write_space();
26323                }
26324                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
26325                if let Expression::Identifier(id) = opt {
26326                    self.write_keyword(&id.name);
26327                } else {
26328                    self.generate_expression(opt)?;
26329                }
26330            }
26331        }
26332        if let Some(kind) = &e.kind {
26333            self.write_space();
26334            self.write_keyword(kind);
26335        }
26336        if let Some(this) = &e.this {
26337            self.write_space();
26338            self.generate_expression(this)?;
26339        }
26340        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
26341        if !e.columns.is_empty() {
26342            self.write("(");
26343            for (i, col) in e.columns.iter().enumerate() {
26344                if i > 0 {
26345                    self.write(", ");
26346                }
26347                self.write(col);
26348            }
26349            self.write(")");
26350        }
26351        if let Some(partition) = &e.partition {
26352            self.write_space();
26353            self.generate_expression(partition)?;
26354        }
26355        if let Some(mode) = &e.mode {
26356            self.write_space();
26357            self.generate_expression(mode)?;
26358        }
26359        if let Some(expression) = &e.expression {
26360            self.write_space();
26361            self.generate_expression(expression)?;
26362        }
26363        if !e.properties.is_empty() {
26364            self.write_space();
26365            self.write_keyword(self.config.with_properties_prefix);
26366            self.write(" (");
26367            for (i, prop) in e.properties.iter().enumerate() {
26368                if i > 0 {
26369                    self.write(", ");
26370                }
26371                self.generate_expression(prop)?;
26372            }
26373            self.write(")");
26374        }
26375        Ok(())
26376    }
26377
26378    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
26379        // Python: return f"DELETE{kind} STATISTICS"
26380        self.write_keyword("DELETE");
26381        if let Some(kind) = &e.kind {
26382            self.write_space();
26383            self.write_keyword(kind);
26384        }
26385        self.write_space();
26386        self.write_keyword("STATISTICS");
26387        Ok(())
26388    }
26389
26390    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
26391        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
26392        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
26393        if let Expression::Identifier(id) = e.this.as_ref() {
26394            self.write_keyword(&id.name);
26395        } else {
26396            self.generate_expression(&e.this)?;
26397        }
26398        self.write_space();
26399        self.write_keyword("HISTOGRAM ON");
26400        self.write_space();
26401        for (i, expr) in e.expressions.iter().enumerate() {
26402            if i > 0 {
26403                self.write(", ");
26404            }
26405            self.generate_expression(expr)?;
26406        }
26407        if let Some(expression) = &e.expression {
26408            self.write_space();
26409            self.generate_expression(expression)?;
26410        }
26411        if let Some(update_options) = &e.update_options {
26412            self.write_space();
26413            self.generate_expression(update_options)?;
26414            self.write_space();
26415            self.write_keyword("UPDATE");
26416        }
26417        Ok(())
26418    }
26419
26420    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
26421        // Python: return f"LIST CHAINED ROWS{inner_expression}"
26422        self.write_keyword("LIST CHAINED ROWS");
26423        if let Some(expression) = &e.expression {
26424            self.write_space();
26425            self.write_keyword("INTO");
26426            self.write_space();
26427            self.generate_expression(expression)?;
26428        }
26429        Ok(())
26430    }
26431
26432    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
26433        // Python: return f"SAMPLE {sample} {kind}"
26434        self.write_keyword("SAMPLE");
26435        self.write_space();
26436        if let Some(sample) = &e.sample {
26437            self.generate_expression(sample)?;
26438            self.write_space();
26439        }
26440        self.write_keyword(&e.kind);
26441        Ok(())
26442    }
26443
26444    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
26445        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
26446        self.write_keyword(&e.kind);
26447        if let Some(option) = &e.option {
26448            self.write_space();
26449            self.generate_expression(option)?;
26450        }
26451        self.write_space();
26452        self.write_keyword("STATISTICS");
26453        if let Some(this) = &e.this {
26454            self.write_space();
26455            self.generate_expression(this)?;
26456        }
26457        if !e.expressions.is_empty() {
26458            self.write_space();
26459            for (i, expr) in e.expressions.iter().enumerate() {
26460                if i > 0 {
26461                    self.write(", ");
26462                }
26463                self.generate_expression(expr)?;
26464            }
26465        }
26466        Ok(())
26467    }
26468
26469    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
26470        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
26471        self.write_keyword("VALIDATE");
26472        self.write_space();
26473        self.write_keyword(&e.kind);
26474        if let Some(this) = &e.this {
26475            self.write_space();
26476            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
26477            if let Expression::Identifier(id) = this.as_ref() {
26478                self.write_keyword(&id.name);
26479            } else {
26480                self.generate_expression(this)?;
26481            }
26482        }
26483        if let Some(expression) = &e.expression {
26484            self.write_space();
26485            self.write_keyword("INTO");
26486            self.write_space();
26487            self.generate_expression(expression)?;
26488        }
26489        Ok(())
26490    }
26491
26492    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
26493        // Python: return f"WITH {expressions}"
26494        self.write_keyword("WITH");
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_anonymous(&mut self, e: &Anonymous) -> Result<()> {
26506        // Anonymous represents a generic function call: FUNC_NAME(args...)
26507        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
26508        self.generate_expression(&e.this)?;
26509        self.write("(");
26510        for (i, arg) in e.expressions.iter().enumerate() {
26511            if i > 0 {
26512                self.write(", ");
26513            }
26514            self.generate_expression(arg)?;
26515        }
26516        self.write(")");
26517        Ok(())
26518    }
26519
26520    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
26521        // Same as Anonymous but for aggregate functions
26522        self.generate_expression(&e.this)?;
26523        self.write("(");
26524        for (i, arg) in e.expressions.iter().enumerate() {
26525            if i > 0 {
26526                self.write(", ");
26527            }
26528            self.generate_expression(arg)?;
26529        }
26530        self.write(")");
26531        Ok(())
26532    }
26533
26534    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
26535        // Python: return f"{this} APPLY({expr})"
26536        self.generate_expression(&e.this)?;
26537        self.write_space();
26538        self.write_keyword("APPLY");
26539        self.write("(");
26540        self.generate_expression(&e.expression)?;
26541        self.write(")");
26542        Ok(())
26543    }
26544
26545    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
26546        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
26547        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
26548        self.write("(");
26549        self.generate_expression(&e.this)?;
26550        if let Some(percentile) = &e.percentile {
26551            self.write(", ");
26552            self.generate_expression(percentile)?;
26553        }
26554        self.write(")");
26555        Ok(())
26556    }
26557
26558    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
26559        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
26560        self.write_keyword("APPROX_QUANTILE");
26561        self.write("(");
26562        self.generate_expression(&e.this)?;
26563        if let Some(quantile) = &e.quantile {
26564            self.write(", ");
26565            self.generate_expression(quantile)?;
26566        }
26567        if let Some(accuracy) = &e.accuracy {
26568            self.write(", ");
26569            self.generate_expression(accuracy)?;
26570        }
26571        if let Some(weight) = &e.weight {
26572            self.write(", ");
26573            self.generate_expression(weight)?;
26574        }
26575        self.write(")");
26576        Ok(())
26577    }
26578
26579    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
26580        // APPROX_QUANTILES(this, expression)
26581        self.write_keyword("APPROX_QUANTILES");
26582        self.write("(");
26583        self.generate_expression(&e.this)?;
26584        if let Some(expression) = &e.expression {
26585            self.write(", ");
26586            self.generate_expression(expression)?;
26587        }
26588        self.write(")");
26589        Ok(())
26590    }
26591
26592    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
26593        // APPROX_TOP_K(this[, expression][, counters])
26594        self.write_keyword("APPROX_TOP_K");
26595        self.write("(");
26596        self.generate_expression(&e.this)?;
26597        if let Some(expression) = &e.expression {
26598            self.write(", ");
26599            self.generate_expression(expression)?;
26600        }
26601        if let Some(counters) = &e.counters {
26602            self.write(", ");
26603            self.generate_expression(counters)?;
26604        }
26605        self.write(")");
26606        Ok(())
26607    }
26608
26609    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
26610        // APPROX_TOP_K_ACCUMULATE(this[, expression])
26611        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
26612        self.write("(");
26613        self.generate_expression(&e.this)?;
26614        if let Some(expression) = &e.expression {
26615            self.write(", ");
26616            self.generate_expression(expression)?;
26617        }
26618        self.write(")");
26619        Ok(())
26620    }
26621
26622    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
26623        // APPROX_TOP_K_COMBINE(this[, expression])
26624        self.write_keyword("APPROX_TOP_K_COMBINE");
26625        self.write("(");
26626        self.generate_expression(&e.this)?;
26627        if let Some(expression) = &e.expression {
26628            self.write(", ");
26629            self.generate_expression(expression)?;
26630        }
26631        self.write(")");
26632        Ok(())
26633    }
26634
26635    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
26636        // APPROX_TOP_K_ESTIMATE(this[, expression])
26637        self.write_keyword("APPROX_TOP_K_ESTIMATE");
26638        self.write("(");
26639        self.generate_expression(&e.this)?;
26640        if let Some(expression) = &e.expression {
26641            self.write(", ");
26642            self.generate_expression(expression)?;
26643        }
26644        self.write(")");
26645        Ok(())
26646    }
26647
26648    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
26649        // APPROX_TOP_SUM(this, expression[, count])
26650        self.write_keyword("APPROX_TOP_SUM");
26651        self.write("(");
26652        self.generate_expression(&e.this)?;
26653        self.write(", ");
26654        self.generate_expression(&e.expression)?;
26655        if let Some(count) = &e.count {
26656            self.write(", ");
26657            self.generate_expression(count)?;
26658        }
26659        self.write(")");
26660        Ok(())
26661    }
26662
26663    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
26664        // ARG_MAX(this, expression[, count])
26665        self.write_keyword("ARG_MAX");
26666        self.write("(");
26667        self.generate_expression(&e.this)?;
26668        self.write(", ");
26669        self.generate_expression(&e.expression)?;
26670        if let Some(count) = &e.count {
26671            self.write(", ");
26672            self.generate_expression(count)?;
26673        }
26674        self.write(")");
26675        Ok(())
26676    }
26677
26678    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
26679        // ARG_MIN(this, expression[, count])
26680        self.write_keyword("ARG_MIN");
26681        self.write("(");
26682        self.generate_expression(&e.this)?;
26683        self.write(", ");
26684        self.generate_expression(&e.expression)?;
26685        if let Some(count) = &e.count {
26686            self.write(", ");
26687            self.generate_expression(count)?;
26688        }
26689        self.write(")");
26690        Ok(())
26691    }
26692
26693    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
26694        // ARRAY_ALL(this, expression)
26695        self.write_keyword("ARRAY_ALL");
26696        self.write("(");
26697        self.generate_expression(&e.this)?;
26698        self.write(", ");
26699        self.generate_expression(&e.expression)?;
26700        self.write(")");
26701        Ok(())
26702    }
26703
26704    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
26705        // ARRAY_ANY(this, expression) - fallback implementation
26706        self.write_keyword("ARRAY_ANY");
26707        self.write("(");
26708        self.generate_expression(&e.this)?;
26709        self.write(", ");
26710        self.generate_expression(&e.expression)?;
26711        self.write(")");
26712        Ok(())
26713    }
26714
26715    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
26716        // ARRAY_CONSTRUCT_COMPACT(expressions...)
26717        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
26718        self.write("(");
26719        for (i, expr) in e.expressions.iter().enumerate() {
26720            if i > 0 {
26721                self.write(", ");
26722            }
26723            self.generate_expression(expr)?;
26724        }
26725        self.write(")");
26726        Ok(())
26727    }
26728
26729    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
26730        // ARRAY_SUM(this[, expression])
26731        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26732            self.write("arraySum");
26733        } else {
26734            self.write_keyword("ARRAY_SUM");
26735        }
26736        self.write("(");
26737        self.generate_expression(&e.this)?;
26738        if let Some(expression) = &e.expression {
26739            self.write(", ");
26740            self.generate_expression(expression)?;
26741        }
26742        self.write(")");
26743        Ok(())
26744    }
26745
26746    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
26747        // Python: return f"{this} AT {index}"
26748        self.generate_expression(&e.this)?;
26749        self.write_space();
26750        self.write_keyword("AT");
26751        self.write_space();
26752        self.generate_expression(&e.expression)?;
26753        Ok(())
26754    }
26755
26756    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
26757        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
26758        self.write_keyword("ATTACH");
26759        if e.exists {
26760            self.write_space();
26761            self.write_keyword("IF NOT EXISTS");
26762        }
26763        self.write_space();
26764        self.generate_expression(&e.this)?;
26765        if !e.expressions.is_empty() {
26766            self.write(" (");
26767            for (i, expr) in e.expressions.iter().enumerate() {
26768                if i > 0 {
26769                    self.write(", ");
26770                }
26771                self.generate_expression(expr)?;
26772            }
26773            self.write(")");
26774        }
26775        Ok(())
26776    }
26777
26778    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
26779        // AttachOption: this [expression]
26780        // Python sqlglot: no equals sign, just space-separated
26781        self.generate_expression(&e.this)?;
26782        if let Some(expression) = &e.expression {
26783            self.write_space();
26784            self.generate_expression(expression)?;
26785        }
26786        Ok(())
26787    }
26788
26789    /// Generate the auto_increment keyword and options for a column definition.
26790    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
26791    /// GENERATED AS IDENTITY, etc.
26792    fn generate_auto_increment_keyword(
26793        &mut self,
26794        col: &crate::expressions::ColumnDef,
26795    ) -> Result<()> {
26796        use crate::dialects::DialectType;
26797        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
26798            self.write_keyword("IDENTITY");
26799            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26800                self.write("(");
26801                if let Some(ref start) = col.auto_increment_start {
26802                    self.generate_expression(start)?;
26803                } else {
26804                    self.write("0");
26805                }
26806                self.write(", ");
26807                if let Some(ref inc) = col.auto_increment_increment {
26808                    self.generate_expression(inc)?;
26809                } else {
26810                    self.write("1");
26811                }
26812                self.write(")");
26813            }
26814        } else if matches!(
26815            self.config.dialect,
26816            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
26817        ) {
26818            self.write_keyword("AUTOINCREMENT");
26819            if let Some(ref start) = col.auto_increment_start {
26820                self.write_space();
26821                self.write_keyword("START");
26822                self.write_space();
26823                self.generate_expression(start)?;
26824            }
26825            if let Some(ref inc) = col.auto_increment_increment {
26826                self.write_space();
26827                self.write_keyword("INCREMENT");
26828                self.write_space();
26829                self.generate_expression(inc)?;
26830            }
26831            if let Some(order) = col.auto_increment_order {
26832                self.write_space();
26833                if order {
26834                    self.write_keyword("ORDER");
26835                } else {
26836                    self.write_keyword("NOORDER");
26837                }
26838            }
26839        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
26840            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
26841            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26842                self.write(" (");
26843                let mut first = true;
26844                if let Some(ref start) = col.auto_increment_start {
26845                    self.write_keyword("START WITH");
26846                    self.write_space();
26847                    self.generate_expression(start)?;
26848                    first = false;
26849                }
26850                if let Some(ref inc) = col.auto_increment_increment {
26851                    if !first {
26852                        self.write_space();
26853                    }
26854                    self.write_keyword("INCREMENT BY");
26855                    self.write_space();
26856                    self.generate_expression(inc)?;
26857                }
26858                self.write(")");
26859            }
26860        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
26861            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
26862            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
26863            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26864                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
26865            } else {
26866                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
26867            }
26868            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26869                self.write(" (");
26870                let mut first = true;
26871                if let Some(ref start) = col.auto_increment_start {
26872                    self.write_keyword("START WITH");
26873                    self.write_space();
26874                    self.generate_expression(start)?;
26875                    first = false;
26876                }
26877                if let Some(ref inc) = col.auto_increment_increment {
26878                    if !first {
26879                        self.write_space();
26880                    }
26881                    self.write_keyword("INCREMENT BY");
26882                    self.write_space();
26883                    self.generate_expression(inc)?;
26884                }
26885                self.write(")");
26886            }
26887        } else if matches!(
26888            self.config.dialect,
26889            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26890        ) {
26891            self.write_keyword("IDENTITY");
26892            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26893                self.write("(");
26894                if let Some(ref start) = col.auto_increment_start {
26895                    self.generate_expression(start)?;
26896                } else {
26897                    self.write("0");
26898                }
26899                self.write(", ");
26900                if let Some(ref inc) = col.auto_increment_increment {
26901                    self.generate_expression(inc)?;
26902                } else {
26903                    self.write("1");
26904                }
26905                self.write(")");
26906            }
26907        } else {
26908            self.write_keyword("AUTO_INCREMENT");
26909            if let Some(ref start) = col.auto_increment_start {
26910                self.write_space();
26911                self.write_keyword("START");
26912                self.write_space();
26913                self.generate_expression(start)?;
26914            }
26915            if let Some(ref inc) = col.auto_increment_increment {
26916                self.write_space();
26917                self.write_keyword("INCREMENT");
26918                self.write_space();
26919                self.generate_expression(inc)?;
26920            }
26921            if let Some(order) = col.auto_increment_order {
26922                self.write_space();
26923                if order {
26924                    self.write_keyword("ORDER");
26925                } else {
26926                    self.write_keyword("NOORDER");
26927                }
26928            }
26929        }
26930        Ok(())
26931    }
26932
26933    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
26934        // AUTO_INCREMENT=value
26935        self.write_keyword("AUTO_INCREMENT");
26936        self.write("=");
26937        self.generate_expression(&e.this)?;
26938        Ok(())
26939    }
26940
26941    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
26942        // AUTO_REFRESH=value
26943        self.write_keyword("AUTO_REFRESH");
26944        self.write("=");
26945        self.generate_expression(&e.this)?;
26946        Ok(())
26947    }
26948
26949    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
26950        // BACKUP YES|NO (Redshift syntax uses space, not equals)
26951        self.write_keyword("BACKUP");
26952        self.write_space();
26953        self.generate_expression(&e.this)?;
26954        Ok(())
26955    }
26956
26957    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
26958        // BASE64_DECODE_BINARY(this[, alphabet])
26959        self.write_keyword("BASE64_DECODE_BINARY");
26960        self.write("(");
26961        self.generate_expression(&e.this)?;
26962        if let Some(alphabet) = &e.alphabet {
26963            self.write(", ");
26964            self.generate_expression(alphabet)?;
26965        }
26966        self.write(")");
26967        Ok(())
26968    }
26969
26970    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
26971        // BASE64_DECODE_STRING(this[, alphabet])
26972        self.write_keyword("BASE64_DECODE_STRING");
26973        self.write("(");
26974        self.generate_expression(&e.this)?;
26975        if let Some(alphabet) = &e.alphabet {
26976            self.write(", ");
26977            self.generate_expression(alphabet)?;
26978        }
26979        self.write(")");
26980        Ok(())
26981    }
26982
26983    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
26984        // BASE64_ENCODE(this[, max_line_length][, alphabet])
26985        self.write_keyword("BASE64_ENCODE");
26986        self.write("(");
26987        self.generate_expression(&e.this)?;
26988        if let Some(max_line_length) = &e.max_line_length {
26989            self.write(", ");
26990            self.generate_expression(max_line_length)?;
26991        }
26992        if let Some(alphabet) = &e.alphabet {
26993            self.write(", ");
26994            self.generate_expression(alphabet)?;
26995        }
26996        self.write(")");
26997        Ok(())
26998    }
26999
27000    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
27001        // BLOCKCOMPRESSION=... (complex Teradata property)
27002        self.write_keyword("BLOCKCOMPRESSION");
27003        self.write("=");
27004        if let Some(autotemp) = &e.autotemp {
27005            self.write_keyword("AUTOTEMP");
27006            self.write("(");
27007            self.generate_expression(autotemp)?;
27008            self.write(")");
27009        }
27010        if let Some(always) = &e.always {
27011            self.generate_expression(always)?;
27012        }
27013        if let Some(default) = &e.default {
27014            self.generate_expression(default)?;
27015        }
27016        if let Some(manual) = &e.manual {
27017            self.generate_expression(manual)?;
27018        }
27019        if let Some(never) = &e.never {
27020            self.generate_expression(never)?;
27021        }
27022        Ok(())
27023    }
27024
27025    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
27026        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
27027        self.write("((");
27028        self.generate_expression(&e.this)?;
27029        self.write(") ");
27030        self.write_keyword("AND");
27031        self.write(" (");
27032        self.generate_expression(&e.expression)?;
27033        self.write("))");
27034        Ok(())
27035    }
27036
27037    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
27038        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
27039        self.write("((");
27040        self.generate_expression(&e.this)?;
27041        self.write(") ");
27042        self.write_keyword("OR");
27043        self.write(" (");
27044        self.generate_expression(&e.expression)?;
27045        self.write("))");
27046        Ok(())
27047    }
27048
27049    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
27050        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
27051        self.write_keyword("BUILD");
27052        self.write_space();
27053        self.generate_expression(&e.this)?;
27054        Ok(())
27055    }
27056
27057    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
27058        // Byte string literal like B'...' or X'...'
27059        self.generate_expression(&e.this)?;
27060        Ok(())
27061    }
27062
27063    fn generate_case_specific_column_constraint(
27064        &mut self,
27065        e: &CaseSpecificColumnConstraint,
27066    ) -> Result<()> {
27067        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
27068        if e.not_.is_some() {
27069            self.write_keyword("NOT");
27070            self.write_space();
27071        }
27072        self.write_keyword("CASESPECIFIC");
27073        Ok(())
27074    }
27075
27076    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
27077        // Cast to string type (dialect-specific)
27078        self.write_keyword("CAST");
27079        self.write("(");
27080        self.generate_expression(&e.this)?;
27081        if self.config.dialect == Some(DialectType::ClickHouse) {
27082            // ClickHouse: CAST(expr, 'type_string')
27083            self.write(", ");
27084        } else {
27085            self.write_space();
27086            self.write_keyword("AS");
27087            self.write_space();
27088        }
27089        if let Some(to) = &e.to {
27090            self.generate_expression(to)?;
27091        }
27092        self.write(")");
27093        Ok(())
27094    }
27095
27096    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
27097        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
27098        // Python: f"CHANGES ({information}){at_before}{end}"
27099        self.write_keyword("CHANGES");
27100        self.write(" (");
27101        if let Some(information) = &e.information {
27102            self.write_keyword("INFORMATION");
27103            self.write(" => ");
27104            self.generate_expression(information)?;
27105        }
27106        self.write(")");
27107        // at_before and end are HistoricalData expressions that generate their own keywords
27108        if let Some(at_before) = &e.at_before {
27109            self.write(" ");
27110            self.generate_expression(at_before)?;
27111        }
27112        if let Some(end) = &e.end {
27113            self.write(" ");
27114            self.generate_expression(end)?;
27115        }
27116        Ok(())
27117    }
27118
27119    fn generate_character_set_column_constraint(
27120        &mut self,
27121        e: &CharacterSetColumnConstraint,
27122    ) -> Result<()> {
27123        // CHARACTER SET charset_name
27124        self.write_keyword("CHARACTER SET");
27125        self.write_space();
27126        self.generate_expression(&e.this)?;
27127        Ok(())
27128    }
27129
27130    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
27131        // [DEFAULT] CHARACTER SET=value
27132        if e.default.is_some() {
27133            self.write_keyword("DEFAULT");
27134            self.write_space();
27135        }
27136        self.write_keyword("CHARACTER SET");
27137        self.write("=");
27138        self.generate_expression(&e.this)?;
27139        Ok(())
27140    }
27141
27142    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
27143        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
27144        self.write_keyword("CHECK");
27145        self.write(" (");
27146        self.generate_expression(&e.this)?;
27147        self.write(")");
27148        if e.enforced.is_some() {
27149            self.write_space();
27150            self.write_keyword("ENFORCED");
27151        }
27152        Ok(())
27153    }
27154
27155    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
27156        // Python: return f"ASSUME ({self.sql(e, 'this')})"
27157        self.write_keyword("ASSUME");
27158        self.write(" (");
27159        self.generate_expression(&e.this)?;
27160        self.write(")");
27161        Ok(())
27162    }
27163
27164    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
27165        // CHECK_JSON(this)
27166        self.write_keyword("CHECK_JSON");
27167        self.write("(");
27168        self.generate_expression(&e.this)?;
27169        self.write(")");
27170        Ok(())
27171    }
27172
27173    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
27174        // CHECK_XML(this)
27175        self.write_keyword("CHECK_XML");
27176        self.write("(");
27177        self.generate_expression(&e.this)?;
27178        self.write(")");
27179        Ok(())
27180    }
27181
27182    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
27183        // CHECKSUM=[ON|OFF|DEFAULT]
27184        self.write_keyword("CHECKSUM");
27185        self.write("=");
27186        if e.on.is_some() {
27187            self.write_keyword("ON");
27188        } else if e.default.is_some() {
27189            self.write_keyword("DEFAULT");
27190        } else {
27191            self.write_keyword("OFF");
27192        }
27193        Ok(())
27194    }
27195
27196    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
27197        // Python: return f"{shallow}{keyword} {this}"
27198        if e.shallow.is_some() {
27199            self.write_keyword("SHALLOW");
27200            self.write_space();
27201        }
27202        if e.copy.is_some() {
27203            self.write_keyword("COPY");
27204        } else {
27205            self.write_keyword("CLONE");
27206        }
27207        self.write_space();
27208        self.generate_expression(&e.this)?;
27209        Ok(())
27210    }
27211
27212    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
27213        // CLUSTER BY (expressions)
27214        self.write_keyword("CLUSTER BY");
27215        self.write(" (");
27216        for (i, ord) in e.expressions.iter().enumerate() {
27217            if i > 0 {
27218                self.write(", ");
27219            }
27220            self.generate_ordered(ord)?;
27221        }
27222        self.write(")");
27223        Ok(())
27224    }
27225
27226    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
27227        // BigQuery table property: CLUSTER BY col1, col2
27228        self.write_keyword("CLUSTER BY");
27229        self.write_space();
27230        for (i, col) in e.columns.iter().enumerate() {
27231            if i > 0 {
27232                self.write(", ");
27233            }
27234            self.generate_identifier(col)?;
27235        }
27236        Ok(())
27237    }
27238
27239    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
27240        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
27241        self.write_keyword("CLUSTERED BY");
27242        self.write(" (");
27243        for (i, expr) in e.expressions.iter().enumerate() {
27244            if i > 0 {
27245                self.write(", ");
27246            }
27247            self.generate_expression(expr)?;
27248        }
27249        self.write(")");
27250        if let Some(sorted_by) = &e.sorted_by {
27251            self.write_space();
27252            self.write_keyword("SORTED BY");
27253            self.write(" (");
27254            // Unwrap Tuple to avoid double parentheses
27255            if let Expression::Tuple(t) = sorted_by.as_ref() {
27256                for (i, expr) in t.expressions.iter().enumerate() {
27257                    if i > 0 {
27258                        self.write(", ");
27259                    }
27260                    self.generate_expression(expr)?;
27261                }
27262            } else {
27263                self.generate_expression(sorted_by)?;
27264            }
27265            self.write(")");
27266        }
27267        if let Some(buckets) = &e.buckets {
27268            self.write_space();
27269            self.write_keyword("INTO");
27270            self.write_space();
27271            self.generate_expression(buckets)?;
27272            self.write_space();
27273            self.write_keyword("BUCKETS");
27274        }
27275        Ok(())
27276    }
27277
27278    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
27279        // [DEFAULT] COLLATE [=] value
27280        // BigQuery uses space: DEFAULT COLLATE 'en'
27281        // Others use equals: COLLATE='en'
27282        if e.default.is_some() {
27283            self.write_keyword("DEFAULT");
27284            self.write_space();
27285        }
27286        self.write_keyword("COLLATE");
27287        // BigQuery uses space between COLLATE and value
27288        match self.config.dialect {
27289            Some(DialectType::BigQuery) => self.write_space(),
27290            _ => self.write("="),
27291        }
27292        self.generate_expression(&e.this)?;
27293        Ok(())
27294    }
27295
27296    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
27297        // ColumnConstraint is an enum
27298        match e {
27299            ColumnConstraint::NotNull => {
27300                self.write_keyword("NOT NULL");
27301            }
27302            ColumnConstraint::Null => {
27303                self.write_keyword("NULL");
27304            }
27305            ColumnConstraint::Unique => {
27306                self.write_keyword("UNIQUE");
27307            }
27308            ColumnConstraint::PrimaryKey => {
27309                self.write_keyword("PRIMARY KEY");
27310            }
27311            ColumnConstraint::Default(expr) => {
27312                self.write_keyword("DEFAULT");
27313                self.write_space();
27314                self.generate_expression(expr)?;
27315            }
27316            ColumnConstraint::Check(expr) => {
27317                self.write_keyword("CHECK");
27318                self.write(" (");
27319                self.generate_expression(expr)?;
27320                self.write(")");
27321            }
27322            ColumnConstraint::References(fk_ref) => {
27323                if fk_ref.has_foreign_key_keywords {
27324                    self.write_keyword("FOREIGN KEY");
27325                    self.write_space();
27326                }
27327                self.write_keyword("REFERENCES");
27328                self.write_space();
27329                self.generate_table(&fk_ref.table)?;
27330                if !fk_ref.columns.is_empty() {
27331                    self.write(" (");
27332                    for (i, col) in fk_ref.columns.iter().enumerate() {
27333                        if i > 0 {
27334                            self.write(", ");
27335                        }
27336                        self.generate_identifier(col)?;
27337                    }
27338                    self.write(")");
27339                }
27340            }
27341            ColumnConstraint::GeneratedAsIdentity(gen) => {
27342                self.write_keyword("GENERATED");
27343                self.write_space();
27344                if gen.always {
27345                    self.write_keyword("ALWAYS");
27346                } else {
27347                    self.write_keyword("BY DEFAULT");
27348                    if gen.on_null {
27349                        self.write_space();
27350                        self.write_keyword("ON NULL");
27351                    }
27352                }
27353                self.write_space();
27354                self.write_keyword("AS IDENTITY");
27355            }
27356            ColumnConstraint::Collate(collation) => {
27357                self.write_keyword("COLLATE");
27358                self.write_space();
27359                self.generate_identifier(collation)?;
27360            }
27361            ColumnConstraint::Comment(comment) => {
27362                self.write_keyword("COMMENT");
27363                self.write(" '");
27364                self.write(comment);
27365                self.write("'");
27366            }
27367            ColumnConstraint::ComputedColumn(cc) => {
27368                self.generate_computed_column_inline(cc)?;
27369            }
27370            ColumnConstraint::GeneratedAsRow(gar) => {
27371                self.generate_generated_as_row_inline(gar)?;
27372            }
27373            ColumnConstraint::Tags(tags) => {
27374                self.write_keyword("TAG");
27375                self.write(" (");
27376                for (i, expr) in tags.expressions.iter().enumerate() {
27377                    if i > 0 {
27378                        self.write(", ");
27379                    }
27380                    self.generate_expression(expr)?;
27381                }
27382                self.write(")");
27383            }
27384            ColumnConstraint::Path(path_expr) => {
27385                self.write_keyword("PATH");
27386                self.write_space();
27387                self.generate_expression(path_expr)?;
27388            }
27389        }
27390        Ok(())
27391    }
27392
27393    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
27394        // ColumnPosition is an enum
27395        match e {
27396            ColumnPosition::First => {
27397                self.write_keyword("FIRST");
27398            }
27399            ColumnPosition::After(ident) => {
27400                self.write_keyword("AFTER");
27401                self.write_space();
27402                self.generate_identifier(ident)?;
27403            }
27404        }
27405        Ok(())
27406    }
27407
27408    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
27409        // column(prefix)
27410        self.generate_expression(&e.this)?;
27411        self.write("(");
27412        self.generate_expression(&e.expression)?;
27413        self.write(")");
27414        Ok(())
27415    }
27416
27417    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
27418        // If unpack is true, this came from * COLUMNS(pattern)
27419        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
27420        if let Some(ref unpack) = e.unpack {
27421            if let Expression::Boolean(b) = unpack.as_ref() {
27422                if b.value {
27423                    self.write("*");
27424                }
27425            }
27426        }
27427        self.write_keyword("COLUMNS");
27428        self.write("(");
27429        self.generate_expression(&e.this)?;
27430        self.write(")");
27431        Ok(())
27432    }
27433
27434    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
27435        // Combined aggregate: FUNC(args) combined
27436        self.generate_expression(&e.this)?;
27437        self.write("(");
27438        for (i, expr) in e.expressions.iter().enumerate() {
27439            if i > 0 {
27440                self.write(", ");
27441            }
27442            self.generate_expression(expr)?;
27443        }
27444        self.write(")");
27445        Ok(())
27446    }
27447
27448    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
27449        // Combined parameterized aggregate: FUNC(params)(expressions)
27450        self.generate_expression(&e.this)?;
27451        self.write("(");
27452        for (i, param) in e.params.iter().enumerate() {
27453            if i > 0 {
27454                self.write(", ");
27455            }
27456            self.generate_expression(param)?;
27457        }
27458        self.write(")(");
27459        for (i, expr) in e.expressions.iter().enumerate() {
27460            if i > 0 {
27461                self.write(", ");
27462            }
27463            self.generate_expression(expr)?;
27464        }
27465        self.write(")");
27466        Ok(())
27467    }
27468
27469    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
27470        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
27471        self.write_keyword("COMMIT");
27472
27473        // TSQL always uses COMMIT TRANSACTION
27474        if e.this.is_none()
27475            && matches!(
27476                self.config.dialect,
27477                Some(DialectType::TSQL) | Some(DialectType::Fabric)
27478            )
27479        {
27480            self.write_space();
27481            self.write_keyword("TRANSACTION");
27482        }
27483
27484        // Check if this has TRANSACTION keyword or transaction name
27485        if let Some(this) = &e.this {
27486            // Check if it's just the "TRANSACTION" marker or an actual transaction name
27487            let is_transaction_marker = matches!(
27488                this.as_ref(),
27489                Expression::Identifier(id) if id.name == "TRANSACTION"
27490            );
27491
27492            self.write_space();
27493            self.write_keyword("TRANSACTION");
27494
27495            // If it's a real transaction name, output it
27496            if !is_transaction_marker {
27497                self.write_space();
27498                self.generate_expression(this)?;
27499            }
27500        }
27501
27502        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
27503        if let Some(durability) = &e.durability {
27504            self.write_space();
27505            self.write_keyword("WITH");
27506            self.write(" (");
27507            self.write_keyword("DELAYED_DURABILITY");
27508            self.write(" = ");
27509            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
27510                self.write_keyword("ON");
27511            } else {
27512                self.write_keyword("OFF");
27513            }
27514            self.write(")");
27515        }
27516
27517        // Output AND [NO] CHAIN
27518        if let Some(chain) = &e.chain {
27519            self.write_space();
27520            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
27521                self.write_keyword("AND NO CHAIN");
27522            } else {
27523                self.write_keyword("AND CHAIN");
27524            }
27525        }
27526        Ok(())
27527    }
27528
27529    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
27530        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
27531        self.write("[");
27532        self.generate_expression(&e.this)?;
27533        self.write_space();
27534        self.write_keyword("FOR");
27535        self.write_space();
27536        self.generate_expression(&e.expression)?;
27537        // Handle optional position variable (for enumerate-like syntax)
27538        if let Some(pos) = &e.position {
27539            self.write(", ");
27540            self.generate_expression(pos)?;
27541        }
27542        if let Some(iterator) = &e.iterator {
27543            self.write_space();
27544            self.write_keyword("IN");
27545            self.write_space();
27546            self.generate_expression(iterator)?;
27547        }
27548        if let Some(condition) = &e.condition {
27549            self.write_space();
27550            self.write_keyword("IF");
27551            self.write_space();
27552            self.generate_expression(condition)?;
27553        }
27554        self.write("]");
27555        Ok(())
27556    }
27557
27558    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
27559        // COMPRESS(this[, method])
27560        self.write_keyword("COMPRESS");
27561        self.write("(");
27562        self.generate_expression(&e.this)?;
27563        if let Some(method) = &e.method {
27564            self.write(", '");
27565            self.write(method);
27566            self.write("'");
27567        }
27568        self.write(")");
27569        Ok(())
27570    }
27571
27572    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
27573        // Python: return f"COMPRESS {this}"
27574        self.write_keyword("COMPRESS");
27575        if let Some(this) = &e.this {
27576            self.write_space();
27577            self.generate_expression(this)?;
27578        }
27579        Ok(())
27580    }
27581
27582    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
27583        // Python: return f"AS {this}{persisted}"
27584        self.write_keyword("AS");
27585        self.write_space();
27586        self.generate_expression(&e.this)?;
27587        if e.not_null.is_some() {
27588            self.write_space();
27589            self.write_keyword("PERSISTED NOT NULL");
27590        } else if e.persisted.is_some() {
27591            self.write_space();
27592            self.write_keyword("PERSISTED");
27593        }
27594        Ok(())
27595    }
27596
27597    /// Generate a ComputedColumn constraint inline within a column definition.
27598    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
27599    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
27600    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
27601        let computed_expr = if matches!(
27602            self.config.dialect,
27603            Some(DialectType::TSQL) | Some(DialectType::Fabric)
27604        ) {
27605            match &*cc.expression {
27606                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
27607                {
27608                    let wrapped = Expression::Cast(Box::new(Cast {
27609                        this: y.this.clone(),
27610                        to: DataType::Date,
27611                        trailing_comments: Vec::new(),
27612                        double_colon_syntax: false,
27613                        format: None,
27614                        default: None,
27615                        inferred_type: None,
27616                    }));
27617                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
27618                }
27619                Expression::Function(f)
27620                    if f.name.eq_ignore_ascii_case("YEAR")
27621                        && f.args.len() == 1
27622                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
27623                {
27624                    let wrapped = Expression::Cast(Box::new(Cast {
27625                        this: f.args[0].clone(),
27626                        to: DataType::Date,
27627                        trailing_comments: Vec::new(),
27628                        double_colon_syntax: false,
27629                        format: None,
27630                        default: None,
27631                        inferred_type: None,
27632                    }));
27633                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
27634                }
27635                _ => *cc.expression.clone(),
27636            }
27637        } else {
27638            *cc.expression.clone()
27639        };
27640
27641        match cc.persistence_kind.as_deref() {
27642            Some("STORED") | Some("VIRTUAL") => {
27643                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
27644                self.write_keyword("GENERATED ALWAYS AS");
27645                self.write(" (");
27646                self.generate_expression(&computed_expr)?;
27647                self.write(")");
27648                self.write_space();
27649                if cc.persisted {
27650                    self.write_keyword("STORED");
27651                } else {
27652                    self.write_keyword("VIRTUAL");
27653                }
27654            }
27655            Some("PERSISTED") => {
27656                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
27657                self.write_keyword("AS");
27658                self.write(" (");
27659                self.generate_expression(&computed_expr)?;
27660                self.write(")");
27661                self.write_space();
27662                self.write_keyword("PERSISTED");
27663                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
27664                if let Some(ref dt) = cc.data_type {
27665                    self.write_space();
27666                    self.generate_data_type(dt)?;
27667                }
27668                if cc.not_null {
27669                    self.write_space();
27670                    self.write_keyword("NOT NULL");
27671                }
27672            }
27673            _ => {
27674                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
27675                // TSQL computed column without PERSISTED: AS (expr)
27676                if matches!(
27677                    self.config.dialect,
27678                    Some(DialectType::Spark)
27679                        | Some(DialectType::Databricks)
27680                        | Some(DialectType::Hive)
27681                ) {
27682                    self.write_keyword("GENERATED ALWAYS AS");
27683                    self.write(" (");
27684                    self.generate_expression(&computed_expr)?;
27685                    self.write(")");
27686                } else if matches!(
27687                    self.config.dialect,
27688                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27689                ) {
27690                    self.write_keyword("AS");
27691                    let omit_parens = matches!(computed_expr, Expression::Year(_))
27692                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
27693                    if omit_parens {
27694                        self.write_space();
27695                        self.generate_expression(&computed_expr)?;
27696                    } else {
27697                        self.write(" (");
27698                        self.generate_expression(&computed_expr)?;
27699                        self.write(")");
27700                    }
27701                } else {
27702                    self.write_keyword("AS");
27703                    self.write(" (");
27704                    self.generate_expression(&computed_expr)?;
27705                    self.write(")");
27706                }
27707            }
27708        }
27709        Ok(())
27710    }
27711
27712    /// Generate a GeneratedAsRow constraint inline within a column definition.
27713    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
27714    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
27715        self.write_keyword("GENERATED ALWAYS AS ROW ");
27716        if gar.start {
27717            self.write_keyword("START");
27718        } else {
27719            self.write_keyword("END");
27720        }
27721        if gar.hidden {
27722            self.write_space();
27723            self.write_keyword("HIDDEN");
27724        }
27725        Ok(())
27726    }
27727
27728    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
27729    fn generate_system_versioning_content(
27730        &mut self,
27731        e: &WithSystemVersioningProperty,
27732    ) -> Result<()> {
27733        let mut parts = Vec::new();
27734
27735        if let Some(this) = &e.this {
27736            let mut s = String::from("HISTORY_TABLE=");
27737            let mut gen = Generator::with_arc_config(self.config.clone());
27738            gen.generate_expression(this)?;
27739            s.push_str(&gen.output);
27740            parts.push(s);
27741        }
27742
27743        if let Some(data_consistency) = &e.data_consistency {
27744            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
27745            let mut gen = Generator::with_arc_config(self.config.clone());
27746            gen.generate_expression(data_consistency)?;
27747            s.push_str(&gen.output);
27748            parts.push(s);
27749        }
27750
27751        if let Some(retention_period) = &e.retention_period {
27752            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
27753            let mut gen = Generator::with_arc_config(self.config.clone());
27754            gen.generate_expression(retention_period)?;
27755            s.push_str(&gen.output);
27756            parts.push(s);
27757        }
27758
27759        self.write_keyword("SYSTEM_VERSIONING");
27760        self.write("=");
27761
27762        if !parts.is_empty() {
27763            self.write_keyword("ON");
27764            self.write("(");
27765            self.write(&parts.join(", "));
27766            self.write(")");
27767        } else if e.on.is_some() {
27768            self.write_keyword("ON");
27769        } else {
27770            self.write_keyword("OFF");
27771        }
27772
27773        Ok(())
27774    }
27775
27776    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
27777        // Conditional INSERT for multi-table inserts
27778        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
27779        if e.else_.is_some() {
27780            self.write_keyword("ELSE");
27781            self.write_space();
27782        } else if let Some(expression) = &e.expression {
27783            self.write_keyword("WHEN");
27784            self.write_space();
27785            self.generate_expression(expression)?;
27786            self.write_space();
27787            self.write_keyword("THEN");
27788            self.write_space();
27789        }
27790
27791        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
27792        // without the "INSERT " prefix
27793        if let Expression::Insert(insert) = e.this.as_ref() {
27794            self.write_keyword("INTO");
27795            self.write_space();
27796            self.generate_table(&insert.table)?;
27797
27798            // Optional column list
27799            if !insert.columns.is_empty() {
27800                self.write(" (");
27801                for (i, col) in insert.columns.iter().enumerate() {
27802                    if i > 0 {
27803                        self.write(", ");
27804                    }
27805                    self.generate_identifier(col)?;
27806                }
27807                self.write(")");
27808            }
27809
27810            // Optional VALUES clause
27811            if !insert.values.is_empty() {
27812                self.write_space();
27813                self.write_keyword("VALUES");
27814                for (row_idx, row) in insert.values.iter().enumerate() {
27815                    if row_idx > 0 {
27816                        self.write(", ");
27817                    }
27818                    self.write(" (");
27819                    for (i, val) in row.iter().enumerate() {
27820                        if i > 0 {
27821                            self.write(", ");
27822                        }
27823                        self.generate_expression(val)?;
27824                    }
27825                    self.write(")");
27826                }
27827            }
27828        } else {
27829            // Fallback for non-Insert expressions
27830            self.generate_expression(&e.this)?;
27831        }
27832        Ok(())
27833    }
27834
27835    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
27836        // Python: return f"CONSTRAINT {this} {expressions}"
27837        self.write_keyword("CONSTRAINT");
27838        self.write_space();
27839        self.generate_expression(&e.this)?;
27840        if !e.expressions.is_empty() {
27841            self.write_space();
27842            for (i, expr) in e.expressions.iter().enumerate() {
27843                if i > 0 {
27844                    self.write_space();
27845                }
27846                self.generate_expression(expr)?;
27847            }
27848        }
27849        Ok(())
27850    }
27851
27852    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
27853        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
27854        self.write_keyword("CONVERT_TIMEZONE");
27855        self.write("(");
27856        let mut first = true;
27857        if let Some(source_tz) = &e.source_tz {
27858            self.generate_expression(source_tz)?;
27859            first = false;
27860        }
27861        if let Some(target_tz) = &e.target_tz {
27862            if !first {
27863                self.write(", ");
27864            }
27865            self.generate_expression(target_tz)?;
27866            first = false;
27867        }
27868        if let Some(timestamp) = &e.timestamp {
27869            if !first {
27870                self.write(", ");
27871            }
27872            self.generate_expression(timestamp)?;
27873        }
27874        self.write(")");
27875        Ok(())
27876    }
27877
27878    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
27879        // CONVERT(this USING dest)
27880        self.write_keyword("CONVERT");
27881        self.write("(");
27882        self.generate_expression(&e.this)?;
27883        if let Some(dest) = &e.dest {
27884            self.write_space();
27885            self.write_keyword("USING");
27886            self.write_space();
27887            self.generate_expression(dest)?;
27888        }
27889        self.write(")");
27890        Ok(())
27891    }
27892
27893    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
27894        self.write_keyword("COPY");
27895        if e.is_into {
27896            self.write_space();
27897            self.write_keyword("INTO");
27898        }
27899        self.write_space();
27900
27901        // Generate target table or query (or stage for COPY INTO @stage)
27902        if let Expression::Literal(lit) = &e.this {
27903            if let Literal::String(s) = lit.as_ref() {
27904                if s.starts_with('@') {
27905                    self.write(s);
27906                } else {
27907                    self.generate_expression(&e.this)?;
27908                }
27909            }
27910        } else {
27911            self.generate_expression(&e.this)?;
27912        }
27913
27914        // FROM or TO based on kind
27915        if e.kind {
27916            // kind=true means FROM (loading into table)
27917            if self.config.pretty {
27918                self.write_newline();
27919            } else {
27920                self.write_space();
27921            }
27922            self.write_keyword("FROM");
27923            self.write_space();
27924        } else if !e.files.is_empty() {
27925            // kind=false means TO (exporting)
27926            if self.config.pretty {
27927                self.write_newline();
27928            } else {
27929                self.write_space();
27930            }
27931            self.write_keyword("TO");
27932            self.write_space();
27933        }
27934
27935        // Generate source/destination files
27936        for (i, file) in e.files.iter().enumerate() {
27937            if i > 0 {
27938                self.write_space();
27939            }
27940            // For stage references (strings starting with @), output without quotes
27941            if let Expression::Literal(lit) = file {
27942                if let Literal::String(s) = lit.as_ref() {
27943                    if s.starts_with('@') {
27944                        self.write(s);
27945                    } else {
27946                        self.generate_expression(file)?;
27947                    }
27948                }
27949            } else if let Expression::Identifier(id) = file {
27950                // Backtick-quoted file path (Databricks style: `s3://link`)
27951                if id.quoted {
27952                    self.write("`");
27953                    self.write(&id.name);
27954                    self.write("`");
27955                } else {
27956                    self.generate_expression(file)?;
27957                }
27958            } else {
27959                self.generate_expression(file)?;
27960            }
27961        }
27962
27963        // Generate credentials if present (Snowflake style - not wrapped in WITH)
27964        if !e.with_wrapped {
27965            if let Some(ref creds) = e.credentials {
27966                if let Some(ref storage) = creds.storage {
27967                    if self.config.pretty {
27968                        self.write_newline();
27969                    } else {
27970                        self.write_space();
27971                    }
27972                    self.write_keyword("STORAGE_INTEGRATION");
27973                    self.write(" = ");
27974                    self.write(storage);
27975                }
27976                if creds.credentials.is_empty() {
27977                    // Empty credentials: CREDENTIALS = ()
27978                    if self.config.pretty {
27979                        self.write_newline();
27980                    } else {
27981                        self.write_space();
27982                    }
27983                    self.write_keyword("CREDENTIALS");
27984                    self.write(" = ()");
27985                } else {
27986                    if self.config.pretty {
27987                        self.write_newline();
27988                    } else {
27989                        self.write_space();
27990                    }
27991                    self.write_keyword("CREDENTIALS");
27992                    // Check if this is Redshift-style (single value with empty key)
27993                    // vs Snowflake-style (multiple key=value pairs)
27994                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
27995                        // Redshift style: CREDENTIALS 'value'
27996                        self.write(" '");
27997                        self.write(&creds.credentials[0].1);
27998                        self.write("'");
27999                    } else {
28000                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
28001                        self.write(" = (");
28002                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
28003                            if i > 0 {
28004                                self.write_space();
28005                            }
28006                            self.write(k);
28007                            self.write("='");
28008                            self.write(v);
28009                            self.write("'");
28010                        }
28011                        self.write(")");
28012                    }
28013                }
28014                if let Some(ref encryption) = creds.encryption {
28015                    self.write_space();
28016                    self.write_keyword("ENCRYPTION");
28017                    self.write(" = ");
28018                    self.write(encryption);
28019                }
28020            }
28021        }
28022
28023        // Generate parameters
28024        if !e.params.is_empty() {
28025            if e.with_wrapped {
28026                // DuckDB/PostgreSQL/TSQL WITH (...) format
28027                self.write_space();
28028                self.write_keyword("WITH");
28029                self.write(" (");
28030                for (i, param) in e.params.iter().enumerate() {
28031                    if i > 0 {
28032                        self.write(", ");
28033                    }
28034                    self.generate_copy_param_with_format(param)?;
28035                }
28036                self.write(")");
28037            } else {
28038                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
28039                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
28040                // For Snowflake: KEY = VALUE
28041                for param in &e.params {
28042                    if self.config.pretty {
28043                        self.write_newline();
28044                    } else {
28045                        self.write_space();
28046                    }
28047                    // Preserve original case of parameter name (important for Redshift COPY options)
28048                    self.write(&param.name);
28049                    if let Some(ref value) = param.value {
28050                        // Use = only if it was present in the original (param.eq)
28051                        if param.eq {
28052                            self.write(" = ");
28053                        } else {
28054                            self.write(" ");
28055                        }
28056                        if !param.values.is_empty() {
28057                            self.write("(");
28058                            for (i, v) in param.values.iter().enumerate() {
28059                                if i > 0 {
28060                                    self.write_space();
28061                                }
28062                                self.generate_copy_nested_param(v)?;
28063                            }
28064                            self.write(")");
28065                        } else {
28066                            // For COPY parameter values, output identifiers without quoting
28067                            self.generate_copy_param_value(value)?;
28068                        }
28069                    } else if !param.values.is_empty() {
28070                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
28071                        if param.eq {
28072                            self.write(" = (");
28073                        } else {
28074                            self.write(" (");
28075                        }
28076                        // Determine separator for values inside parentheses:
28077                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
28078                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
28079                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
28080                        let is_key_value_pairs = param
28081                            .values
28082                            .first()
28083                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
28084                        let sep = if is_key_value_pairs && param.eq {
28085                            " "
28086                        } else {
28087                            ", "
28088                        };
28089                        for (i, v) in param.values.iter().enumerate() {
28090                            if i > 0 {
28091                                self.write(sep);
28092                            }
28093                            self.generate_copy_nested_param(v)?;
28094                        }
28095                        self.write(")");
28096                    }
28097                }
28098            }
28099        }
28100
28101        Ok(())
28102    }
28103
28104    /// Generate a COPY parameter in WITH (...) format
28105    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
28106    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
28107        self.write_keyword(&param.name);
28108        if !param.values.is_empty() {
28109            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
28110            self.write(" = (");
28111            for (i, v) in param.values.iter().enumerate() {
28112                if i > 0 {
28113                    self.write(", ");
28114                }
28115                self.generate_copy_nested_param(v)?;
28116            }
28117            self.write(")");
28118        } else if let Some(ref value) = param.value {
28119            if param.eq {
28120                self.write(" = ");
28121            } else {
28122                self.write(" ");
28123            }
28124            self.generate_expression(value)?;
28125        }
28126        Ok(())
28127    }
28128
28129    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
28130    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
28131        match expr {
28132            Expression::Eq(eq) => {
28133                // Generate key
28134                match &eq.left {
28135                    Expression::Column(c) => self.write(&c.name.name),
28136                    _ => self.generate_expression(&eq.left)?,
28137                }
28138                self.write("=");
28139                // Generate value
28140                match &eq.right {
28141                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
28142                        let Literal::String(s) = lit.as_ref() else {
28143                            unreachable!()
28144                        };
28145                        self.write("'");
28146                        self.write(s);
28147                        self.write("'");
28148                    }
28149                    Expression::Tuple(t) => {
28150                        // For lists like NULL_IF=('', 'str1')
28151                        self.write("(");
28152                        if self.config.pretty {
28153                            self.write_newline();
28154                            self.indent_level += 1;
28155                            for (i, item) in t.expressions.iter().enumerate() {
28156                                if i > 0 {
28157                                    self.write(", ");
28158                                }
28159                                self.write_indent();
28160                                self.generate_expression(item)?;
28161                            }
28162                            self.write_newline();
28163                            self.indent_level -= 1;
28164                        } else {
28165                            for (i, item) in t.expressions.iter().enumerate() {
28166                                if i > 0 {
28167                                    self.write(", ");
28168                                }
28169                                self.generate_expression(item)?;
28170                            }
28171                        }
28172                        self.write(")");
28173                    }
28174                    _ => self.generate_expression(&eq.right)?,
28175                }
28176                Ok(())
28177            }
28178            Expression::Column(c) => {
28179                // Standalone keyword like COMPRESSION
28180                self.write(&c.name.name);
28181                Ok(())
28182            }
28183            _ => self.generate_expression(expr),
28184        }
28185    }
28186
28187    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
28188    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
28189    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
28190        match expr {
28191            Expression::Column(c) => {
28192                // Output identifier, preserving quotes if originally quoted
28193                if c.name.quoted {
28194                    self.write("\"");
28195                    self.write(&c.name.name);
28196                    self.write("\"");
28197                } else {
28198                    self.write(&c.name.name);
28199                }
28200                Ok(())
28201            }
28202            Expression::Identifier(id) => {
28203                // Output identifier, preserving quotes if originally quoted
28204                if id.quoted {
28205                    self.write("\"");
28206                    self.write(&id.name);
28207                    self.write("\"");
28208                } else {
28209                    self.write(&id.name);
28210                }
28211                Ok(())
28212            }
28213            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
28214                let Literal::String(s) = lit.as_ref() else {
28215                    unreachable!()
28216                };
28217                // Output string with quotes
28218                self.write("'");
28219                self.write(s);
28220                self.write("'");
28221                Ok(())
28222            }
28223            _ => self.generate_expression(expr),
28224        }
28225    }
28226
28227    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
28228        self.write_keyword(&e.name);
28229        if let Some(ref value) = e.value {
28230            if e.eq {
28231                self.write(" = ");
28232            } else {
28233                self.write(" ");
28234            }
28235            self.generate_expression(value)?;
28236        }
28237        if !e.values.is_empty() {
28238            if e.eq {
28239                self.write(" = ");
28240            } else {
28241                self.write(" ");
28242            }
28243            self.write("(");
28244            for (i, v) in e.values.iter().enumerate() {
28245                if i > 0 {
28246                    self.write(", ");
28247                }
28248                self.generate_expression(v)?;
28249            }
28250            self.write(")");
28251        }
28252        Ok(())
28253    }
28254
28255    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
28256        // CORR(this, expression)
28257        self.write_keyword("CORR");
28258        self.write("(");
28259        self.generate_expression(&e.this)?;
28260        self.write(", ");
28261        self.generate_expression(&e.expression)?;
28262        self.write(")");
28263        Ok(())
28264    }
28265
28266    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
28267        // COSINE_DISTANCE(this, expression)
28268        self.write_keyword("COSINE_DISTANCE");
28269        self.write("(");
28270        self.generate_expression(&e.this)?;
28271        self.write(", ");
28272        self.generate_expression(&e.expression)?;
28273        self.write(")");
28274        Ok(())
28275    }
28276
28277    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
28278        // COVAR_POP(this, expression)
28279        self.write_keyword("COVAR_POP");
28280        self.write("(");
28281        self.generate_expression(&e.this)?;
28282        self.write(", ");
28283        self.generate_expression(&e.expression)?;
28284        self.write(")");
28285        Ok(())
28286    }
28287
28288    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
28289        // COVAR_SAMP(this, expression)
28290        self.write_keyword("COVAR_SAMP");
28291        self.write("(");
28292        self.generate_expression(&e.this)?;
28293        self.write(", ");
28294        self.generate_expression(&e.expression)?;
28295        self.write(")");
28296        Ok(())
28297    }
28298
28299    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
28300        // CREDENTIALS (key1='value1', key2='value2')
28301        self.write_keyword("CREDENTIALS");
28302        self.write(" (");
28303        for (i, (key, value)) in e.credentials.iter().enumerate() {
28304            if i > 0 {
28305                self.write(", ");
28306            }
28307            self.write(key);
28308            self.write("='");
28309            self.write(value);
28310            self.write("'");
28311        }
28312        self.write(")");
28313        Ok(())
28314    }
28315
28316    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
28317        // CREDENTIALS=(expressions)
28318        self.write_keyword("CREDENTIALS");
28319        self.write("=(");
28320        for (i, expr) in e.expressions.iter().enumerate() {
28321            if i > 0 {
28322                self.write(", ");
28323            }
28324            self.generate_expression(expr)?;
28325        }
28326        self.write(")");
28327        Ok(())
28328    }
28329
28330    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
28331        use crate::dialects::DialectType;
28332
28333        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
28334        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
28335        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
28336            self.generate_expression(&e.this)?;
28337            self.write_space();
28338            self.write_keyword("AS");
28339            self.write_space();
28340            self.generate_identifier(&e.alias)?;
28341            return Ok(());
28342        }
28343        self.write(&e.alias.name);
28344
28345        // BigQuery doesn't support column aliases in CTE definitions
28346        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
28347
28348        if !e.columns.is_empty() && !skip_cte_columns {
28349            self.write("(");
28350            for (i, col) in e.columns.iter().enumerate() {
28351                if i > 0 {
28352                    self.write(", ");
28353                }
28354                self.write(&col.name);
28355            }
28356            self.write(")");
28357        }
28358        // USING KEY (columns) for DuckDB recursive CTEs
28359        if !e.key_expressions.is_empty() {
28360            self.write_space();
28361            self.write_keyword("USING KEY");
28362            self.write(" (");
28363            for (i, key) in e.key_expressions.iter().enumerate() {
28364                if i > 0 {
28365                    self.write(", ");
28366                }
28367                self.write(&key.name);
28368            }
28369            self.write(")");
28370        }
28371        self.write_space();
28372        self.write_keyword("AS");
28373        self.write_space();
28374        if let Some(materialized) = e.materialized {
28375            if materialized {
28376                self.write_keyword("MATERIALIZED");
28377            } else {
28378                self.write_keyword("NOT MATERIALIZED");
28379            }
28380            self.write_space();
28381        }
28382        self.write("(");
28383        self.generate_expression(&e.this)?;
28384        self.write(")");
28385        Ok(())
28386    }
28387
28388    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
28389        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
28390        if e.expressions.is_empty() {
28391            self.write_keyword("WITH CUBE");
28392        } else {
28393            self.write_keyword("CUBE");
28394            self.write("(");
28395            for (i, expr) in e.expressions.iter().enumerate() {
28396                if i > 0 {
28397                    self.write(", ");
28398                }
28399                self.generate_expression(expr)?;
28400            }
28401            self.write(")");
28402        }
28403        Ok(())
28404    }
28405
28406    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
28407        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
28408        self.write_keyword("CURRENT_DATETIME");
28409        if let Some(this) = &e.this {
28410            self.write("(");
28411            self.generate_expression(this)?;
28412            self.write(")");
28413        }
28414        Ok(())
28415    }
28416
28417    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
28418        // CURRENT_SCHEMA - no arguments
28419        self.write_keyword("CURRENT_SCHEMA");
28420        Ok(())
28421    }
28422
28423    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
28424        // CURRENT_SCHEMAS(include_implicit)
28425        self.write_keyword("CURRENT_SCHEMAS");
28426        self.write("(");
28427        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
28428        if !matches!(
28429            self.config.dialect,
28430            Some(crate::dialects::DialectType::Snowflake)
28431        ) {
28432            if let Some(this) = &e.this {
28433                self.generate_expression(this)?;
28434            }
28435        }
28436        self.write(")");
28437        Ok(())
28438    }
28439
28440    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
28441        // CURRENT_USER or CURRENT_USER()
28442        self.write_keyword("CURRENT_USER");
28443        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
28444        let needs_parens = e.this.is_some()
28445            || matches!(
28446                self.config.dialect,
28447                Some(DialectType::Snowflake)
28448                    | Some(DialectType::Spark)
28449                    | Some(DialectType::Hive)
28450                    | Some(DialectType::DuckDB)
28451                    | Some(DialectType::BigQuery)
28452                    | Some(DialectType::MySQL)
28453                    | Some(DialectType::Databricks)
28454            );
28455        if needs_parens {
28456            self.write("()");
28457        }
28458        Ok(())
28459    }
28460
28461    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
28462        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
28463        if self.config.dialect == Some(DialectType::Solr) {
28464            self.generate_expression(&e.this)?;
28465            self.write(" ");
28466            self.write_keyword("OR");
28467            self.write(" ");
28468            self.generate_expression(&e.expression)?;
28469        } else if self.config.dialect == Some(DialectType::MySQL) {
28470            self.generate_mysql_concat_from_dpipe(e)?;
28471        } else {
28472            // String concatenation: this || expression
28473            self.generate_expression(&e.this)?;
28474            self.write(" || ");
28475            self.generate_expression(&e.expression)?;
28476        }
28477        Ok(())
28478    }
28479
28480    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
28481        // DATABLOCKSIZE=... (Teradata)
28482        self.write_keyword("DATABLOCKSIZE");
28483        self.write("=");
28484        if let Some(size) = e.size {
28485            self.write(&size.to_string());
28486            if let Some(units) = &e.units {
28487                self.write_space();
28488                self.generate_expression(units)?;
28489            }
28490        } else if e.minimum.is_some() {
28491            self.write_keyword("MINIMUM");
28492        } else if e.maximum.is_some() {
28493            self.write_keyword("MAXIMUM");
28494        } else if e.default.is_some() {
28495            self.write_keyword("DEFAULT");
28496        }
28497        Ok(())
28498    }
28499
28500    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
28501        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
28502        self.write_keyword("DATA_DELETION");
28503        self.write("=");
28504
28505        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
28506        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
28507
28508        if is_on {
28509            self.write_keyword("ON");
28510            if has_options {
28511                self.write("(");
28512                let mut first = true;
28513                if let Some(filter_column) = &e.filter_column {
28514                    self.write_keyword("FILTER_COLUMN");
28515                    self.write("=");
28516                    self.generate_expression(filter_column)?;
28517                    first = false;
28518                }
28519                if let Some(retention_period) = &e.retention_period {
28520                    if !first {
28521                        self.write(", ");
28522                    }
28523                    self.write_keyword("RETENTION_PERIOD");
28524                    self.write("=");
28525                    self.generate_expression(retention_period)?;
28526                }
28527                self.write(")");
28528            }
28529        } else {
28530            self.write_keyword("OFF");
28531        }
28532        Ok(())
28533    }
28534
28535    /// Generate a Date function expression
28536    /// For Exasol: {d'value'} -> TO_DATE('value')
28537    /// For other dialects: DATE('value')
28538    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
28539        use crate::dialects::DialectType;
28540        use crate::expressions::Literal;
28541
28542        match self.config.dialect {
28543            // Exasol uses TO_DATE for Date expressions
28544            Some(DialectType::Exasol) => {
28545                self.write_keyword("TO_DATE");
28546                self.write("(");
28547                // Extract the string value from the expression if it's a string literal
28548                match &e.this {
28549                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
28550                        let Literal::String(s) = lit.as_ref() else {
28551                            unreachable!()
28552                        };
28553                        self.write("'");
28554                        self.write(s);
28555                        self.write("'");
28556                    }
28557                    _ => {
28558                        self.generate_expression(&e.this)?;
28559                    }
28560                }
28561                self.write(")");
28562            }
28563            // Standard: DATE(value)
28564            _ => {
28565                self.write_keyword("DATE");
28566                self.write("(");
28567                self.generate_expression(&e.this)?;
28568                self.write(")");
28569            }
28570        }
28571        Ok(())
28572    }
28573
28574    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
28575        // DATE_BIN(interval, timestamp[, origin])
28576        self.write_keyword("DATE_BIN");
28577        self.write("(");
28578        self.generate_expression(&e.this)?;
28579        self.write(", ");
28580        self.generate_expression(&e.expression)?;
28581        if let Some(origin) = &e.origin {
28582            self.write(", ");
28583            self.generate_expression(origin)?;
28584        }
28585        self.write(")");
28586        Ok(())
28587    }
28588
28589    fn generate_date_format_column_constraint(
28590        &mut self,
28591        e: &DateFormatColumnConstraint,
28592    ) -> Result<()> {
28593        // FORMAT 'format_string' (Teradata)
28594        self.write_keyword("FORMAT");
28595        self.write_space();
28596        self.generate_expression(&e.this)?;
28597        Ok(())
28598    }
28599
28600    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
28601        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
28602        self.write_keyword("DATE_FROM_PARTS");
28603        self.write("(");
28604        let mut first = true;
28605        if let Some(year) = &e.year {
28606            self.generate_expression(year)?;
28607            first = false;
28608        }
28609        if let Some(month) = &e.month {
28610            if !first {
28611                self.write(", ");
28612            }
28613            self.generate_expression(month)?;
28614            first = false;
28615        }
28616        if let Some(day) = &e.day {
28617            if !first {
28618                self.write(", ");
28619            }
28620            self.generate_expression(day)?;
28621        }
28622        self.write(")");
28623        Ok(())
28624    }
28625
28626    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
28627        // DATETIME(this) or DATETIME(this, expression)
28628        self.write_keyword("DATETIME");
28629        self.write("(");
28630        self.generate_expression(&e.this)?;
28631        if let Some(expr) = &e.expression {
28632            self.write(", ");
28633            self.generate_expression(expr)?;
28634        }
28635        self.write(")");
28636        Ok(())
28637    }
28638
28639    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
28640        // DATETIME_ADD(this, expression, unit)
28641        self.write_keyword("DATETIME_ADD");
28642        self.write("(");
28643        self.generate_expression(&e.this)?;
28644        self.write(", ");
28645        self.generate_expression(&e.expression)?;
28646        if let Some(unit) = &e.unit {
28647            self.write(", ");
28648            self.write_keyword(unit);
28649        }
28650        self.write(")");
28651        Ok(())
28652    }
28653
28654    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
28655        // DATETIME_DIFF(this, expression, unit)
28656        self.write_keyword("DATETIME_DIFF");
28657        self.write("(");
28658        self.generate_expression(&e.this)?;
28659        self.write(", ");
28660        self.generate_expression(&e.expression)?;
28661        if let Some(unit) = &e.unit {
28662            self.write(", ");
28663            self.write_keyword(unit);
28664        }
28665        self.write(")");
28666        Ok(())
28667    }
28668
28669    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
28670        // DATETIME_SUB(this, expression, unit)
28671        self.write_keyword("DATETIME_SUB");
28672        self.write("(");
28673        self.generate_expression(&e.this)?;
28674        self.write(", ");
28675        self.generate_expression(&e.expression)?;
28676        if let Some(unit) = &e.unit {
28677            self.write(", ");
28678            self.write_keyword(unit);
28679        }
28680        self.write(")");
28681        Ok(())
28682    }
28683
28684    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
28685        // DATETIME_TRUNC(this, unit, zone)
28686        self.write_keyword("DATETIME_TRUNC");
28687        self.write("(");
28688        self.generate_expression(&e.this)?;
28689        self.write(", ");
28690        self.write_keyword(&e.unit);
28691        if let Some(zone) = &e.zone {
28692            self.write(", ");
28693            self.generate_expression(zone)?;
28694        }
28695        self.write(")");
28696        Ok(())
28697    }
28698
28699    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
28700        // DAYNAME(this)
28701        self.write_keyword("DAYNAME");
28702        self.write("(");
28703        self.generate_expression(&e.this)?;
28704        self.write(")");
28705        Ok(())
28706    }
28707
28708    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
28709        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
28710        self.write_keyword("DECLARE");
28711        self.write_space();
28712        if e.replace {
28713            self.write_keyword("OR");
28714            self.write_space();
28715            self.write_keyword("REPLACE");
28716            self.write_space();
28717        }
28718        for (i, expr) in e.expressions.iter().enumerate() {
28719            if i > 0 {
28720                self.write(", ");
28721            }
28722            self.generate_expression(expr)?;
28723        }
28724        Ok(())
28725    }
28726
28727    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
28728        use crate::dialects::DialectType;
28729
28730        // variable TYPE [DEFAULT default]
28731        self.generate_expression(&e.this)?;
28732        // BigQuery multi-variable: DECLARE X, Y, Z INT64
28733        for name in &e.additional_names {
28734            self.write(", ");
28735            self.generate_expression(name)?;
28736        }
28737        if let Some(kind) = &e.kind {
28738            self.write_space();
28739            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
28740            // TSQL: Always includes AS (normalization)
28741            // Others: Include AS if present in original
28742            match self.config.dialect {
28743                Some(DialectType::BigQuery) => {
28744                    self.write(kind);
28745                }
28746                Some(DialectType::TSQL) => {
28747                    // TSQL DECLARE: no AS keyword (sqlglot convention)
28748                    // Normalize INT to INTEGER for simple declarations
28749                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
28750                    let is_complex_table = kind.starts_with("TABLE")
28751                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
28752                    if is_complex_table {
28753                        self.write(kind);
28754                    } else if kind == "INT" {
28755                        self.write("INTEGER");
28756                    } else if kind.starts_with("TABLE") {
28757                        // Normalize INT to INTEGER inside simple TABLE column definitions
28758                        let normalized = kind
28759                            .replace(" INT ", " INTEGER ")
28760                            .replace(" INT,", " INTEGER,")
28761                            .replace(" INT)", " INTEGER)")
28762                            .replace("(INT ", "(INTEGER ");
28763                        self.write(&normalized);
28764                    } else {
28765                        self.write(kind);
28766                    }
28767                }
28768                _ => {
28769                    if e.has_as {
28770                        self.write_keyword("AS");
28771                        self.write_space();
28772                    }
28773                    self.write(kind);
28774                }
28775            }
28776        }
28777        if let Some(default) = &e.default {
28778            // BigQuery uses DEFAULT, others use =
28779            match self.config.dialect {
28780                Some(DialectType::BigQuery) => {
28781                    self.write_space();
28782                    self.write_keyword("DEFAULT");
28783                    self.write_space();
28784                }
28785                _ => {
28786                    self.write(" = ");
28787                }
28788            }
28789            self.generate_expression(default)?;
28790        }
28791        Ok(())
28792    }
28793
28794    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
28795        // DECODE(expr, search1, result1, search2, result2, ..., default)
28796        self.write_keyword("DECODE");
28797        self.write("(");
28798        for (i, expr) in e.expressions.iter().enumerate() {
28799            if i > 0 {
28800                self.write(", ");
28801            }
28802            self.generate_expression(expr)?;
28803        }
28804        self.write(")");
28805        Ok(())
28806    }
28807
28808    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
28809        // DECOMPRESS(expr, 'method')
28810        self.write_keyword("DECOMPRESS");
28811        self.write("(");
28812        self.generate_expression(&e.this)?;
28813        self.write(", '");
28814        self.write(&e.method);
28815        self.write("')");
28816        Ok(())
28817    }
28818
28819    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
28820        // DECOMPRESS(expr, 'method')
28821        self.write_keyword("DECOMPRESS");
28822        self.write("(");
28823        self.generate_expression(&e.this)?;
28824        self.write(", '");
28825        self.write(&e.method);
28826        self.write("')");
28827        Ok(())
28828    }
28829
28830    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
28831        // DECRYPT(value, passphrase [, aad [, algorithm]])
28832        self.write_keyword("DECRYPT");
28833        self.write("(");
28834        self.generate_expression(&e.this)?;
28835        if let Some(passphrase) = &e.passphrase {
28836            self.write(", ");
28837            self.generate_expression(passphrase)?;
28838        }
28839        if let Some(aad) = &e.aad {
28840            self.write(", ");
28841            self.generate_expression(aad)?;
28842        }
28843        if let Some(method) = &e.encryption_method {
28844            self.write(", ");
28845            self.generate_expression(method)?;
28846        }
28847        self.write(")");
28848        Ok(())
28849    }
28850
28851    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
28852        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
28853        self.write_keyword("DECRYPT_RAW");
28854        self.write("(");
28855        self.generate_expression(&e.this)?;
28856        if let Some(key) = &e.key {
28857            self.write(", ");
28858            self.generate_expression(key)?;
28859        }
28860        if let Some(iv) = &e.iv {
28861            self.write(", ");
28862            self.generate_expression(iv)?;
28863        }
28864        if let Some(aad) = &e.aad {
28865            self.write(", ");
28866            self.generate_expression(aad)?;
28867        }
28868        if let Some(method) = &e.encryption_method {
28869            self.write(", ");
28870            self.generate_expression(method)?;
28871        }
28872        self.write(")");
28873        Ok(())
28874    }
28875
28876    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
28877        // DEFINER = user
28878        self.write_keyword("DEFINER");
28879        self.write(" = ");
28880        self.generate_expression(&e.this)?;
28881        Ok(())
28882    }
28883
28884    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
28885        // Python: DETACH[DATABASE IF EXISTS] this
28886        self.write_keyword("DETACH");
28887        if e.exists {
28888            self.write_keyword(" DATABASE IF EXISTS");
28889        }
28890        self.write_space();
28891        self.generate_expression(&e.this)?;
28892        Ok(())
28893    }
28894
28895    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
28896        let property_name = match e.this.as_ref() {
28897            Expression::Identifier(id) => id.name.as_str(),
28898            Expression::Var(v) => v.this.as_str(),
28899            _ => "DICTIONARY",
28900        };
28901        self.write_keyword(property_name);
28902        self.write("(");
28903        self.write(&e.kind);
28904        if let Some(settings) = &e.settings {
28905            self.write("(");
28906            if let Expression::Tuple(t) = settings.as_ref() {
28907                if self.config.pretty && !t.expressions.is_empty() {
28908                    self.write_newline();
28909                    self.indent_level += 1;
28910                    for (i, pair) in t.expressions.iter().enumerate() {
28911                        if i > 0 {
28912                            self.write(",");
28913                            self.write_newline();
28914                        }
28915                        self.write_indent();
28916                        if let Expression::Tuple(pair_tuple) = pair {
28917                            if let Some(k) = pair_tuple.expressions.first() {
28918                                self.generate_expression(k)?;
28919                            }
28920                            if let Some(v) = pair_tuple.expressions.get(1) {
28921                                self.write(" ");
28922                                self.generate_expression(v)?;
28923                            }
28924                        } else {
28925                            self.generate_expression(pair)?;
28926                        }
28927                    }
28928                    self.indent_level -= 1;
28929                    self.write_newline();
28930                    self.write_indent();
28931                } else {
28932                    for (i, pair) in t.expressions.iter().enumerate() {
28933                        if i > 0 {
28934                            // ClickHouse dict properties are space-separated, not comma-separated
28935                            self.write(" ");
28936                        }
28937                        if let Expression::Tuple(pair_tuple) = pair {
28938                            if let Some(k) = pair_tuple.expressions.first() {
28939                                self.generate_expression(k)?;
28940                            }
28941                            if let Some(v) = pair_tuple.expressions.get(1) {
28942                                self.write(" ");
28943                                self.generate_expression(v)?;
28944                            }
28945                        } else {
28946                            self.generate_expression(pair)?;
28947                        }
28948                    }
28949                }
28950            } else {
28951                self.generate_expression(settings)?;
28952            }
28953            self.write(")");
28954        } else {
28955            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
28956            self.write("()");
28957        }
28958        self.write(")");
28959        Ok(())
28960    }
28961
28962    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
28963        let property_name = match e.this.as_ref() {
28964            Expression::Identifier(id) => id.name.as_str(),
28965            Expression::Var(v) => v.this.as_str(),
28966            _ => "RANGE",
28967        };
28968        self.write_keyword(property_name);
28969        self.write("(");
28970        if let Some(min) = &e.min {
28971            self.write_keyword("MIN");
28972            self.write_space();
28973            self.generate_expression(min)?;
28974        }
28975        if let Some(max) = &e.max {
28976            self.write_space();
28977            self.write_keyword("MAX");
28978            self.write_space();
28979            self.generate_expression(max)?;
28980        }
28981        self.write(")");
28982        Ok(())
28983    }
28984
28985    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
28986        // Python: {local}DIRECTORY {this}{row_format}
28987        if e.local.is_some() {
28988            self.write_keyword("LOCAL ");
28989        }
28990        self.write_keyword("DIRECTORY");
28991        self.write_space();
28992        self.generate_expression(&e.this)?;
28993        if let Some(row_format) = &e.row_format {
28994            self.write_space();
28995            self.generate_expression(row_format)?;
28996        }
28997        Ok(())
28998    }
28999
29000    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
29001        // Redshift: DISTKEY(column)
29002        self.write_keyword("DISTKEY");
29003        self.write("(");
29004        self.generate_expression(&e.this)?;
29005        self.write(")");
29006        Ok(())
29007    }
29008
29009    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
29010        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
29011        self.write_keyword("DISTSTYLE");
29012        self.write_space();
29013        self.generate_expression(&e.this)?;
29014        Ok(())
29015    }
29016
29017    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
29018        // Python: "DISTRIBUTE BY" expressions
29019        self.write_keyword("DISTRIBUTE BY");
29020        self.write_space();
29021        for (i, expr) in e.expressions.iter().enumerate() {
29022            if i > 0 {
29023                self.write(", ");
29024            }
29025            self.generate_expression(expr)?;
29026        }
29027        Ok(())
29028    }
29029
29030    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
29031        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
29032        self.write_keyword("DISTRIBUTED BY");
29033        self.write_space();
29034        self.write(&e.kind);
29035        if !e.expressions.is_empty() {
29036            self.write(" (");
29037            for (i, expr) in e.expressions.iter().enumerate() {
29038                if i > 0 {
29039                    self.write(", ");
29040                }
29041                self.generate_expression(expr)?;
29042            }
29043            self.write(")");
29044        }
29045        if let Some(buckets) = &e.buckets {
29046            self.write_space();
29047            self.write_keyword("BUCKETS");
29048            self.write_space();
29049            self.generate_expression(buckets)?;
29050        }
29051        if let Some(order) = &e.order {
29052            self.write_space();
29053            self.generate_expression(order)?;
29054        }
29055        Ok(())
29056    }
29057
29058    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
29059        // DOT_PRODUCT(vector1, vector2)
29060        self.write_keyword("DOT_PRODUCT");
29061        self.write("(");
29062        self.generate_expression(&e.this)?;
29063        self.write(", ");
29064        self.generate_expression(&e.expression)?;
29065        self.write(")");
29066        Ok(())
29067    }
29068
29069    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
29070        // Python: DROP{IF EXISTS }expressions
29071        self.write_keyword("DROP");
29072        if e.exists {
29073            self.write_keyword(" IF EXISTS ");
29074        } else {
29075            self.write_space();
29076        }
29077        for (i, expr) in e.expressions.iter().enumerate() {
29078            if i > 0 {
29079                self.write(", ");
29080            }
29081            self.generate_expression(expr)?;
29082        }
29083        Ok(())
29084    }
29085
29086    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
29087        // Python: DUPLICATE KEY (expressions)
29088        self.write_keyword("DUPLICATE KEY");
29089        self.write(" (");
29090        for (i, expr) in e.expressions.iter().enumerate() {
29091            if i > 0 {
29092                self.write(", ");
29093            }
29094            self.generate_expression(expr)?;
29095        }
29096        self.write(")");
29097        Ok(())
29098    }
29099
29100    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
29101        // ELT(index, str1, str2, ...)
29102        self.write_keyword("ELT");
29103        self.write("(");
29104        self.generate_expression(&e.this)?;
29105        for expr in &e.expressions {
29106            self.write(", ");
29107            self.generate_expression(expr)?;
29108        }
29109        self.write(")");
29110        Ok(())
29111    }
29112
29113    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
29114        // ENCODE(string, charset)
29115        self.write_keyword("ENCODE");
29116        self.write("(");
29117        self.generate_expression(&e.this)?;
29118        if let Some(charset) = &e.charset {
29119            self.write(", ");
29120            self.generate_expression(charset)?;
29121        }
29122        self.write(")");
29123        Ok(())
29124    }
29125
29126    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
29127        // Python: [KEY ]ENCODE this [properties]
29128        if e.key.is_some() {
29129            self.write_keyword("KEY ");
29130        }
29131        self.write_keyword("ENCODE");
29132        self.write_space();
29133        self.generate_expression(&e.this)?;
29134        if !e.properties.is_empty() {
29135            self.write(" (");
29136            for (i, prop) in e.properties.iter().enumerate() {
29137                if i > 0 {
29138                    self.write(", ");
29139                }
29140                self.generate_expression(prop)?;
29141            }
29142            self.write(")");
29143        }
29144        Ok(())
29145    }
29146
29147    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
29148        // ENCRYPT(value, passphrase [, aad [, algorithm]])
29149        self.write_keyword("ENCRYPT");
29150        self.write("(");
29151        self.generate_expression(&e.this)?;
29152        if let Some(passphrase) = &e.passphrase {
29153            self.write(", ");
29154            self.generate_expression(passphrase)?;
29155        }
29156        if let Some(aad) = &e.aad {
29157            self.write(", ");
29158            self.generate_expression(aad)?;
29159        }
29160        if let Some(method) = &e.encryption_method {
29161            self.write(", ");
29162            self.generate_expression(method)?;
29163        }
29164        self.write(")");
29165        Ok(())
29166    }
29167
29168    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
29169        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
29170        self.write_keyword("ENCRYPT_RAW");
29171        self.write("(");
29172        self.generate_expression(&e.this)?;
29173        if let Some(key) = &e.key {
29174            self.write(", ");
29175            self.generate_expression(key)?;
29176        }
29177        if let Some(iv) = &e.iv {
29178            self.write(", ");
29179            self.generate_expression(iv)?;
29180        }
29181        if let Some(aad) = &e.aad {
29182            self.write(", ");
29183            self.generate_expression(aad)?;
29184        }
29185        if let Some(method) = &e.encryption_method {
29186            self.write(", ");
29187            self.generate_expression(method)?;
29188        }
29189        self.write(")");
29190        Ok(())
29191    }
29192
29193    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
29194        // MySQL: ENGINE = InnoDB
29195        self.write_keyword("ENGINE");
29196        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
29197            self.write("=");
29198        } else {
29199            self.write(" = ");
29200        }
29201        self.generate_expression(&e.this)?;
29202        Ok(())
29203    }
29204
29205    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
29206        // ENVIRONMENT (expressions)
29207        self.write_keyword("ENVIRONMENT");
29208        self.write(" (");
29209        for (i, expr) in e.expressions.iter().enumerate() {
29210            if i > 0 {
29211                self.write(", ");
29212            }
29213            self.generate_expression(expr)?;
29214        }
29215        self.write(")");
29216        Ok(())
29217    }
29218
29219    fn generate_ephemeral_column_constraint(
29220        &mut self,
29221        e: &EphemeralColumnConstraint,
29222    ) -> Result<()> {
29223        // MySQL: EPHEMERAL [expr]
29224        self.write_keyword("EPHEMERAL");
29225        if let Some(this) = &e.this {
29226            self.write_space();
29227            self.generate_expression(this)?;
29228        }
29229        Ok(())
29230    }
29231
29232    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
29233        // Snowflake: EQUAL_NULL(a, b)
29234        self.write_keyword("EQUAL_NULL");
29235        self.write("(");
29236        self.generate_expression(&e.this)?;
29237        self.write(", ");
29238        self.generate_expression(&e.expression)?;
29239        self.write(")");
29240        Ok(())
29241    }
29242
29243    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
29244        use crate::dialects::DialectType;
29245
29246        // PostgreSQL uses <-> operator syntax
29247        match self.config.dialect {
29248            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
29249                self.generate_expression(&e.this)?;
29250                self.write(" <-> ");
29251                self.generate_expression(&e.expression)?;
29252            }
29253            _ => {
29254                // Other dialects use EUCLIDEAN_DISTANCE function
29255                self.write_keyword("EUCLIDEAN_DISTANCE");
29256                self.write("(");
29257                self.generate_expression(&e.this)?;
29258                self.write(", ");
29259                self.generate_expression(&e.expression)?;
29260                self.write(")");
29261            }
29262        }
29263        Ok(())
29264    }
29265
29266    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
29267        // EXECUTE AS CALLER|OWNER|user
29268        self.write_keyword("EXECUTE AS");
29269        self.write_space();
29270        self.generate_expression(&e.this)?;
29271        Ok(())
29272    }
29273
29274    fn generate_export(&mut self, e: &Export) -> Result<()> {
29275        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
29276        self.write_keyword("EXPORT DATA");
29277        if let Some(connection) = &e.connection {
29278            self.write_space();
29279            self.write_keyword("WITH CONNECTION");
29280            self.write_space();
29281            self.generate_expression(connection)?;
29282        }
29283        if !e.options.is_empty() {
29284            self.write_space();
29285            self.generate_options_clause(&e.options)?;
29286        }
29287        self.write_space();
29288        self.write_keyword("AS");
29289        self.write_space();
29290        self.generate_expression(&e.this)?;
29291        Ok(())
29292    }
29293
29294    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
29295        // EXTERNAL [this]
29296        self.write_keyword("EXTERNAL");
29297        if let Some(this) = &e.this {
29298            self.write_space();
29299            self.generate_expression(this)?;
29300        }
29301        Ok(())
29302    }
29303
29304    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
29305        // Python: {no}FALLBACK{protection}
29306        if e.no.is_some() {
29307            self.write_keyword("NO ");
29308        }
29309        self.write_keyword("FALLBACK");
29310        if e.protection.is_some() {
29311            self.write_keyword(" PROTECTION");
29312        }
29313        Ok(())
29314    }
29315
29316    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
29317        // BigQuery: FARM_FINGERPRINT(value)
29318        self.write_keyword("FARM_FINGERPRINT");
29319        self.write("(");
29320        for (i, expr) in e.expressions.iter().enumerate() {
29321            if i > 0 {
29322                self.write(", ");
29323            }
29324            self.generate_expression(expr)?;
29325        }
29326        self.write(")");
29327        Ok(())
29328    }
29329
29330    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
29331        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
29332        self.write_keyword("FEATURES_AT_TIME");
29333        self.write("(");
29334        self.generate_expression(&e.this)?;
29335        if let Some(time) = &e.time {
29336            self.write(", ");
29337            self.generate_expression(time)?;
29338        }
29339        if let Some(num_rows) = &e.num_rows {
29340            self.write(", ");
29341            self.generate_expression(num_rows)?;
29342        }
29343        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
29344            self.write(", ");
29345            self.generate_expression(ignore_nulls)?;
29346        }
29347        self.write(")");
29348        Ok(())
29349    }
29350
29351    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
29352        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
29353        let use_limit = !e.percent
29354            && !e.with_ties
29355            && e.count.is_some()
29356            && matches!(
29357                self.config.dialect,
29358                Some(DialectType::Spark)
29359                    | Some(DialectType::Hive)
29360                    | Some(DialectType::DuckDB)
29361                    | Some(DialectType::SQLite)
29362                    | Some(DialectType::MySQL)
29363                    | Some(DialectType::BigQuery)
29364                    | Some(DialectType::Databricks)
29365                    | Some(DialectType::StarRocks)
29366                    | Some(DialectType::Doris)
29367                    | Some(DialectType::Athena)
29368                    | Some(DialectType::ClickHouse)
29369            );
29370
29371        if use_limit {
29372            self.write_keyword("LIMIT");
29373            self.write_space();
29374            self.generate_expression(e.count.as_ref().unwrap())?;
29375            return Ok(());
29376        }
29377
29378        // Python: FETCH direction count limit_options
29379        self.write_keyword("FETCH");
29380        if !e.direction.is_empty() {
29381            self.write_space();
29382            self.write_keyword(&e.direction);
29383        }
29384        if let Some(count) = &e.count {
29385            self.write_space();
29386            self.generate_expression(count)?;
29387        }
29388        // Generate PERCENT, ROWS, WITH TIES/ONLY
29389        if e.percent {
29390            self.write_keyword(" PERCENT");
29391        }
29392        if e.rows {
29393            self.write_keyword(" ROWS");
29394        }
29395        if e.with_ties {
29396            self.write_keyword(" WITH TIES");
29397        } else if e.rows {
29398            self.write_keyword(" ONLY");
29399        } else {
29400            self.write_keyword(" ROWS ONLY");
29401        }
29402        Ok(())
29403    }
29404
29405    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
29406        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
29407        // For Spark/Databricks without hive_format: USING this
29408        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
29409        if e.hive_format.is_some() {
29410            // Hive format: STORED AS ...
29411            self.write_keyword("STORED AS");
29412            self.write_space();
29413            if let Some(this) = &e.this {
29414                // Uppercase the format name (e.g., parquet -> PARQUET)
29415                if let Expression::Identifier(id) = this.as_ref() {
29416                    self.write_keyword(&id.name.to_ascii_uppercase());
29417                } else {
29418                    self.generate_expression(this)?;
29419                }
29420            }
29421        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
29422            // Hive: STORED AS format
29423            self.write_keyword("STORED AS");
29424            self.write_space();
29425            if let Some(this) = &e.this {
29426                if let Expression::Identifier(id) = this.as_ref() {
29427                    self.write_keyword(&id.name.to_ascii_uppercase());
29428                } else {
29429                    self.generate_expression(this)?;
29430                }
29431            }
29432        } else if matches!(
29433            self.config.dialect,
29434            Some(DialectType::Spark) | Some(DialectType::Databricks)
29435        ) {
29436            // Spark/Databricks: USING format (e.g., USING DELTA)
29437            self.write_keyword("USING");
29438            self.write_space();
29439            if let Some(this) = &e.this {
29440                self.generate_expression(this)?;
29441            }
29442        } else {
29443            // Snowflake/standard format
29444            self.write_keyword("FILE_FORMAT");
29445            self.write(" = ");
29446            if let Some(this) = &e.this {
29447                self.generate_expression(this)?;
29448            } else if !e.expressions.is_empty() {
29449                self.write("(");
29450                for (i, expr) in e.expressions.iter().enumerate() {
29451                    if i > 0 {
29452                        self.write(", ");
29453                    }
29454                    self.generate_expression(expr)?;
29455                }
29456                self.write(")");
29457            }
29458        }
29459        Ok(())
29460    }
29461
29462    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
29463        // agg_func FILTER(WHERE condition)
29464        self.generate_expression(&e.this)?;
29465        self.write_space();
29466        self.write_keyword("FILTER");
29467        self.write("(");
29468        self.write_keyword("WHERE");
29469        self.write_space();
29470        self.generate_expression(&e.expression)?;
29471        self.write(")");
29472        Ok(())
29473    }
29474
29475    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
29476        // FLOAT64(this) or FLOAT64(this, expression)
29477        self.write_keyword("FLOAT64");
29478        self.write("(");
29479        self.generate_expression(&e.this)?;
29480        if let Some(expr) = &e.expression {
29481            self.write(", ");
29482            self.generate_expression(expr)?;
29483        }
29484        self.write(")");
29485        Ok(())
29486    }
29487
29488    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
29489        // FOR this DO expression
29490        self.write_keyword("FOR");
29491        self.write_space();
29492        self.generate_expression(&e.this)?;
29493        self.write_space();
29494        self.write_keyword("DO");
29495        self.write_space();
29496        self.generate_expression(&e.expression)?;
29497        Ok(())
29498    }
29499
29500    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
29501        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
29502        self.write_keyword("FOREIGN KEY");
29503        if !e.expressions.is_empty() {
29504            self.write(" (");
29505            for (i, expr) in e.expressions.iter().enumerate() {
29506                if i > 0 {
29507                    self.write(", ");
29508                }
29509                self.generate_expression(expr)?;
29510            }
29511            self.write(")");
29512        }
29513        if let Some(reference) = &e.reference {
29514            self.write_space();
29515            self.generate_expression(reference)?;
29516        }
29517        if let Some(delete) = &e.delete {
29518            self.write_space();
29519            self.write_keyword("ON DELETE");
29520            self.write_space();
29521            self.generate_expression(delete)?;
29522        }
29523        if let Some(update) = &e.update {
29524            self.write_space();
29525            self.write_keyword("ON UPDATE");
29526            self.write_space();
29527            self.generate_expression(update)?;
29528        }
29529        if !e.options.is_empty() {
29530            self.write_space();
29531            for (i, opt) in e.options.iter().enumerate() {
29532                if i > 0 {
29533                    self.write_space();
29534                }
29535                self.generate_expression(opt)?;
29536            }
29537        }
29538        Ok(())
29539    }
29540
29541    fn generate_format(&mut self, e: &Format) -> Result<()> {
29542        // FORMAT(this, expressions...)
29543        self.write_keyword("FORMAT");
29544        self.write("(");
29545        self.generate_expression(&e.this)?;
29546        for expr in &e.expressions {
29547            self.write(", ");
29548            self.generate_expression(expr)?;
29549        }
29550        self.write(")");
29551        Ok(())
29552    }
29553
29554    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
29555        // Teradata: column (FORMAT 'format_string')
29556        self.generate_expression(&e.this)?;
29557        self.write(" (");
29558        self.write_keyword("FORMAT");
29559        self.write(" '");
29560        self.write(&e.format);
29561        self.write("')");
29562        Ok(())
29563    }
29564
29565    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
29566        // Python: FREESPACE=this[PERCENT]
29567        self.write_keyword("FREESPACE");
29568        self.write("=");
29569        self.generate_expression(&e.this)?;
29570        if e.percent.is_some() {
29571            self.write_keyword(" PERCENT");
29572        }
29573        Ok(())
29574    }
29575
29576    fn generate_from(&mut self, e: &From) -> Result<()> {
29577        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
29578        self.write_keyword("FROM");
29579        self.write_space();
29580
29581        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
29582        // But keep commas when TABLESAMPLE is present
29583        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
29584        use crate::dialects::DialectType;
29585        let has_tablesample = e
29586            .expressions
29587            .iter()
29588            .any(|expr| matches!(expr, Expression::TableSample(_)));
29589        let is_cross_join_dialect = matches!(
29590            self.config.dialect,
29591            Some(DialectType::BigQuery)
29592                | Some(DialectType::Hive)
29593                | Some(DialectType::Spark)
29594                | Some(DialectType::Databricks)
29595                | Some(DialectType::SQLite)
29596                | Some(DialectType::ClickHouse)
29597        );
29598        let source_is_same_as_target2 = self.config.source_dialect.is_some()
29599            && self.config.source_dialect == self.config.dialect;
29600        let source_is_cross_join_dialect2 = matches!(
29601            self.config.source_dialect,
29602            Some(DialectType::BigQuery)
29603                | Some(DialectType::Hive)
29604                | Some(DialectType::Spark)
29605                | Some(DialectType::Databricks)
29606                | Some(DialectType::SQLite)
29607                | Some(DialectType::ClickHouse)
29608        );
29609        let use_cross_join = !has_tablesample
29610            && is_cross_join_dialect
29611            && (source_is_same_as_target2
29612                || source_is_cross_join_dialect2
29613                || self.config.source_dialect.is_none());
29614
29615        // Snowflake wraps standalone VALUES in FROM clause with parentheses
29616        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
29617
29618        for (i, expr) in e.expressions.iter().enumerate() {
29619            if i > 0 {
29620                if use_cross_join {
29621                    self.write(" CROSS JOIN ");
29622                } else {
29623                    self.write(", ");
29624                }
29625            }
29626            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
29627                self.write("(");
29628                self.generate_expression(expr)?;
29629                self.write(")");
29630            } else {
29631                self.generate_expression(expr)?;
29632            }
29633            // Output leading comments that were on the table name before FROM
29634            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
29635            let leading = Self::extract_table_leading_comments(expr);
29636            for comment in &leading {
29637                self.write_space();
29638                self.write_formatted_comment(comment);
29639            }
29640        }
29641        Ok(())
29642    }
29643
29644    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
29645    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
29646        match expr {
29647            Expression::Table(t) => t.leading_comments.clone(),
29648            Expression::Pivot(p) => {
29649                if let Expression::Table(t) = &p.this {
29650                    t.leading_comments.clone()
29651                } else {
29652                    Vec::new()
29653                }
29654            }
29655            _ => Vec::new(),
29656        }
29657    }
29658
29659    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
29660        // FROM_BASE(this, expression) - convert from base N
29661        self.write_keyword("FROM_BASE");
29662        self.write("(");
29663        self.generate_expression(&e.this)?;
29664        self.write(", ");
29665        self.generate_expression(&e.expression)?;
29666        self.write(")");
29667        Ok(())
29668    }
29669
29670    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
29671        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
29672        self.generate_expression(&e.this)?;
29673        if let Some(zone) = &e.zone {
29674            self.write_space();
29675            self.write_keyword("AT TIME ZONE");
29676            self.write_space();
29677            self.generate_expression(zone)?;
29678            self.write_space();
29679            self.write_keyword("AT TIME ZONE");
29680            self.write(" 'UTC'");
29681        }
29682        Ok(())
29683    }
29684
29685    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
29686        // GAP_FILL(this, ts_column, bucket_width, ...)
29687        self.write_keyword("GAP_FILL");
29688        self.write("(");
29689        self.generate_expression(&e.this)?;
29690        if let Some(ts_column) = &e.ts_column {
29691            self.write(", ");
29692            self.generate_expression(ts_column)?;
29693        }
29694        if let Some(bucket_width) = &e.bucket_width {
29695            self.write(", ");
29696            self.generate_expression(bucket_width)?;
29697        }
29698        if let Some(partitioning_columns) = &e.partitioning_columns {
29699            self.write(", ");
29700            self.generate_expression(partitioning_columns)?;
29701        }
29702        if let Some(value_columns) = &e.value_columns {
29703            self.write(", ");
29704            self.generate_expression(value_columns)?;
29705        }
29706        self.write(")");
29707        Ok(())
29708    }
29709
29710    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
29711        // GENERATE_DATE_ARRAY(start, end, step)
29712        self.write_keyword("GENERATE_DATE_ARRAY");
29713        self.write("(");
29714        let mut first = true;
29715        if let Some(start) = &e.start {
29716            self.generate_expression(start)?;
29717            first = false;
29718        }
29719        if let Some(end) = &e.end {
29720            if !first {
29721                self.write(", ");
29722            }
29723            self.generate_expression(end)?;
29724            first = false;
29725        }
29726        if let Some(step) = &e.step {
29727            if !first {
29728                self.write(", ");
29729            }
29730            self.generate_expression(step)?;
29731        }
29732        self.write(")");
29733        Ok(())
29734    }
29735
29736    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
29737        // ML.GENERATE_EMBEDDING(model, content, params)
29738        self.write_keyword("ML.GENERATE_EMBEDDING");
29739        self.write("(");
29740        self.generate_expression(&e.this)?;
29741        self.write(", ");
29742        self.generate_expression(&e.expression)?;
29743        if let Some(params) = &e.params_struct {
29744            self.write(", ");
29745            self.generate_expression(params)?;
29746        }
29747        self.write(")");
29748        Ok(())
29749    }
29750
29751    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
29752        // Dialect-specific function name
29753        let fn_name = match self.config.dialect {
29754            Some(DialectType::Presto)
29755            | Some(DialectType::Trino)
29756            | Some(DialectType::Athena)
29757            | Some(DialectType::Spark)
29758            | Some(DialectType::Databricks)
29759            | Some(DialectType::Hive) => "SEQUENCE",
29760            _ => "GENERATE_SERIES",
29761        };
29762        self.write_keyword(fn_name);
29763        self.write("(");
29764        let mut first = true;
29765        if let Some(start) = &e.start {
29766            self.generate_expression(start)?;
29767            first = false;
29768        }
29769        if let Some(end) = &e.end {
29770            if !first {
29771                self.write(", ");
29772            }
29773            self.generate_expression(end)?;
29774            first = false;
29775        }
29776        if let Some(step) = &e.step {
29777            if !first {
29778                self.write(", ");
29779            }
29780            // For Presto/Trino: convert WEEK intervals to DAY multiples
29781            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
29782            if matches!(
29783                self.config.dialect,
29784                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
29785            ) {
29786                if let Some(converted) = self.convert_week_interval_to_day(step) {
29787                    self.generate_expression(&converted)?;
29788                } else {
29789                    self.generate_expression(step)?;
29790                }
29791            } else {
29792                self.generate_expression(step)?;
29793            }
29794        }
29795        self.write(")");
29796        Ok(())
29797    }
29798
29799    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
29800    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
29801    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
29802        use crate::expressions::*;
29803        if let Expression::Interval(ref iv) = expr {
29804            // Check for structured WEEK unit
29805            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
29806                unit: IntervalUnit::Week,
29807                ..
29808            }) = &iv.unit
29809            {
29810                // Value is in iv.this
29811                let count = match &iv.this {
29812                    Some(Expression::Literal(lit)) => match lit.as_ref() {
29813                        Literal::String(s) | Literal::Number(s) => s.clone(),
29814                        _ => return None,
29815                    },
29816                    _ => return None,
29817                };
29818                (true, count)
29819            } else if iv.unit.is_none() {
29820                // Check for string-encoded interval like "1 WEEK"
29821                if let Some(Expression::Literal(lit)) = &iv.this {
29822                    if let Literal::String(s) = lit.as_ref() {
29823                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
29824                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
29825                            (true, parts[0].to_string())
29826                        } else {
29827                            (false, String::new())
29828                        }
29829                    } else {
29830                        (false, String::new())
29831                    }
29832                } else {
29833                    (false, String::new())
29834                }
29835            } else {
29836                (false, String::new())
29837            };
29838
29839            if is_week {
29840                // Build: (N * INTERVAL '7' DAY)
29841                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
29842                let day_interval = Expression::Interval(Box::new(Interval {
29843                    this: Some(Expression::Literal(Box::new(Literal::String(
29844                        "7".to_string(),
29845                    )))),
29846                    unit: Some(IntervalUnitSpec::Simple {
29847                        unit: IntervalUnit::Day,
29848                        use_plural: false,
29849                    }),
29850                }));
29851                let mul = Expression::Mul(Box::new(BinaryOp {
29852                    left: count_expr,
29853                    right: day_interval,
29854                    left_comments: vec![],
29855                    operator_comments: vec![],
29856                    trailing_comments: vec![],
29857                    inferred_type: None,
29858                }));
29859                return Some(Expression::Paren(Box::new(Paren {
29860                    this: mul,
29861                    trailing_comments: vec![],
29862                })));
29863            }
29864        }
29865        None
29866    }
29867
29868    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
29869        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
29870        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
29871        self.write("(");
29872        let mut first = true;
29873        if let Some(start) = &e.start {
29874            self.generate_expression(start)?;
29875            first = false;
29876        }
29877        if let Some(end) = &e.end {
29878            if !first {
29879                self.write(", ");
29880            }
29881            self.generate_expression(end)?;
29882            first = false;
29883        }
29884        if let Some(step) = &e.step {
29885            if !first {
29886                self.write(", ");
29887            }
29888            self.generate_expression(step)?;
29889        }
29890        self.write(")");
29891        Ok(())
29892    }
29893
29894    fn generate_generated_as_identity_column_constraint(
29895        &mut self,
29896        e: &GeneratedAsIdentityColumnConstraint,
29897    ) -> Result<()> {
29898        use crate::dialects::DialectType;
29899
29900        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
29901        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
29902            self.write_keyword("AUTOINCREMENT");
29903            if let Some(start) = &e.start {
29904                self.write_keyword(" START ");
29905                self.generate_expression(start)?;
29906            }
29907            if let Some(increment) = &e.increment {
29908                self.write_keyword(" INCREMENT ");
29909                self.generate_expression(increment)?;
29910            }
29911            return Ok(());
29912        }
29913
29914        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
29915        self.write_keyword("GENERATED");
29916        if let Some(this) = &e.this {
29917            // Check if it's a truthy boolean expression
29918            if let Expression::Boolean(b) = this.as_ref() {
29919                if b.value {
29920                    self.write_keyword(" ALWAYS");
29921                } else {
29922                    self.write_keyword(" BY DEFAULT");
29923                    if e.on_null.is_some() {
29924                        self.write_keyword(" ON NULL");
29925                    }
29926                }
29927            } else {
29928                self.write_keyword(" ALWAYS");
29929            }
29930        }
29931        self.write_keyword(" AS IDENTITY");
29932        // Add sequence options if any
29933        let has_options = e.start.is_some()
29934            || e.increment.is_some()
29935            || e.minvalue.is_some()
29936            || e.maxvalue.is_some();
29937        if has_options {
29938            self.write(" (");
29939            let mut first = true;
29940            if let Some(start) = &e.start {
29941                self.write_keyword("START WITH ");
29942                self.generate_expression(start)?;
29943                first = false;
29944            }
29945            if let Some(increment) = &e.increment {
29946                if !first {
29947                    self.write(" ");
29948                }
29949                self.write_keyword("INCREMENT BY ");
29950                self.generate_expression(increment)?;
29951                first = false;
29952            }
29953            if let Some(minvalue) = &e.minvalue {
29954                if !first {
29955                    self.write(" ");
29956                }
29957                self.write_keyword("MINVALUE ");
29958                self.generate_expression(minvalue)?;
29959                first = false;
29960            }
29961            if let Some(maxvalue) = &e.maxvalue {
29962                if !first {
29963                    self.write(" ");
29964                }
29965                self.write_keyword("MAXVALUE ");
29966                self.generate_expression(maxvalue)?;
29967            }
29968            self.write(")");
29969        }
29970        Ok(())
29971    }
29972
29973    fn generate_generated_as_row_column_constraint(
29974        &mut self,
29975        e: &GeneratedAsRowColumnConstraint,
29976    ) -> Result<()> {
29977        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
29978        self.write_keyword("GENERATED ALWAYS AS ROW ");
29979        if e.start.is_some() {
29980            self.write_keyword("START");
29981        } else {
29982            self.write_keyword("END");
29983        }
29984        if e.hidden.is_some() {
29985            self.write_keyword(" HIDDEN");
29986        }
29987        Ok(())
29988    }
29989
29990    fn generate_get(&mut self, e: &Get) -> Result<()> {
29991        // GET this target properties
29992        self.write_keyword("GET");
29993        self.write_space();
29994        self.generate_expression(&e.this)?;
29995        if let Some(target) = &e.target {
29996            self.write_space();
29997            self.generate_expression(target)?;
29998        }
29999        for prop in &e.properties {
30000            self.write_space();
30001            self.generate_expression(prop)?;
30002        }
30003        Ok(())
30004    }
30005
30006    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
30007        // GetExtract generates bracket access: this[expression]
30008        self.generate_expression(&e.this)?;
30009        self.write("[");
30010        self.generate_expression(&e.expression)?;
30011        self.write("]");
30012        Ok(())
30013    }
30014
30015    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
30016        // GETBIT(this, expression) or GET_BIT(this, expression)
30017        self.write_keyword("GETBIT");
30018        self.write("(");
30019        self.generate_expression(&e.this)?;
30020        self.write(", ");
30021        self.generate_expression(&e.expression)?;
30022        self.write(")");
30023        Ok(())
30024    }
30025
30026    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
30027        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
30028        if e.is_role {
30029            self.write_keyword("ROLE");
30030            self.write_space();
30031        } else if e.is_group {
30032            self.write_keyword("GROUP");
30033            self.write_space();
30034        } else if e.is_share {
30035            self.write_keyword("SHARE");
30036            self.write_space();
30037        }
30038        self.write(&e.name.name);
30039        Ok(())
30040    }
30041
30042    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
30043        // privilege(columns) or just privilege
30044        self.generate_expression(&e.this)?;
30045        if !e.expressions.is_empty() {
30046            self.write("(");
30047            for (i, expr) in e.expressions.iter().enumerate() {
30048                if i > 0 {
30049                    self.write(", ");
30050                }
30051                self.generate_expression(expr)?;
30052            }
30053            self.write(")");
30054        }
30055        Ok(())
30056    }
30057
30058    fn generate_group(&mut self, e: &Group) -> Result<()> {
30059        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
30060        self.write_keyword("GROUP BY");
30061        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
30062        match e.all {
30063            Some(true) => {
30064                self.write_space();
30065                self.write_keyword("ALL");
30066            }
30067            Some(false) => {
30068                self.write_space();
30069                self.write_keyword("DISTINCT");
30070            }
30071            None => {}
30072        }
30073        if !e.expressions.is_empty() {
30074            self.write_space();
30075            for (i, expr) in e.expressions.iter().enumerate() {
30076                if i > 0 {
30077                    self.write(", ");
30078                }
30079                self.generate_expression(expr)?;
30080            }
30081        }
30082        // Handle CUBE, ROLLUP, GROUPING SETS
30083        if let Some(cube) = &e.cube {
30084            if !e.expressions.is_empty() {
30085                self.write(", ");
30086            } else {
30087                self.write_space();
30088            }
30089            self.generate_expression(cube)?;
30090        }
30091        if let Some(rollup) = &e.rollup {
30092            if !e.expressions.is_empty() || e.cube.is_some() {
30093                self.write(", ");
30094            } else {
30095                self.write_space();
30096            }
30097            self.generate_expression(rollup)?;
30098        }
30099        if let Some(grouping_sets) = &e.grouping_sets {
30100            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
30101                self.write(", ");
30102            } else {
30103                self.write_space();
30104            }
30105            self.generate_expression(grouping_sets)?;
30106        }
30107        if let Some(totals) = &e.totals {
30108            self.write_space();
30109            self.write_keyword("WITH TOTALS");
30110            self.generate_expression(totals)?;
30111        }
30112        Ok(())
30113    }
30114
30115    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
30116        // GROUP BY expressions
30117        self.write_keyword("GROUP BY");
30118        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
30119        match e.all {
30120            Some(true) => {
30121                self.write_space();
30122                self.write_keyword("ALL");
30123            }
30124            Some(false) => {
30125                self.write_space();
30126                self.write_keyword("DISTINCT");
30127            }
30128            None => {}
30129        }
30130
30131        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
30132        // These are represented as Cube/Rollup expressions with empty expressions at the end
30133        let mut trailing_cube = false;
30134        let mut trailing_rollup = false;
30135        let mut regular_expressions: Vec<&Expression> = Vec::new();
30136
30137        for expr in &e.expressions {
30138            match expr {
30139                Expression::Cube(c) if c.expressions.is_empty() => {
30140                    trailing_cube = true;
30141                }
30142                Expression::Rollup(r) if r.expressions.is_empty() => {
30143                    trailing_rollup = true;
30144                }
30145                _ => {
30146                    regular_expressions.push(expr);
30147                }
30148            }
30149        }
30150
30151        // In pretty mode, put columns on separate lines
30152        if self.config.pretty {
30153            self.write_newline();
30154            self.indent_level += 1;
30155            for (i, expr) in regular_expressions.iter().enumerate() {
30156                if i > 0 {
30157                    self.write(",");
30158                    self.write_newline();
30159                }
30160                self.write_indent();
30161                self.generate_expression(expr)?;
30162            }
30163            self.indent_level -= 1;
30164        } else {
30165            self.write_space();
30166            for (i, expr) in regular_expressions.iter().enumerate() {
30167                if i > 0 {
30168                    self.write(", ");
30169                }
30170                self.generate_expression(expr)?;
30171            }
30172        }
30173
30174        // Output trailing WITH CUBE or WITH ROLLUP
30175        if trailing_cube {
30176            self.write_space();
30177            self.write_keyword("WITH CUBE");
30178        } else if trailing_rollup {
30179            self.write_space();
30180            self.write_keyword("WITH ROLLUP");
30181        }
30182
30183        // ClickHouse: WITH TOTALS
30184        if e.totals {
30185            self.write_space();
30186            self.write_keyword("WITH TOTALS");
30187        }
30188
30189        Ok(())
30190    }
30191
30192    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
30193        // GROUPING(col1, col2, ...)
30194        self.write_keyword("GROUPING");
30195        self.write("(");
30196        for (i, expr) in e.expressions.iter().enumerate() {
30197            if i > 0 {
30198                self.write(", ");
30199            }
30200            self.generate_expression(expr)?;
30201        }
30202        self.write(")");
30203        Ok(())
30204    }
30205
30206    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
30207        // GROUPING_ID(col1, col2, ...)
30208        self.write_keyword("GROUPING_ID");
30209        self.write("(");
30210        for (i, expr) in e.expressions.iter().enumerate() {
30211            if i > 0 {
30212                self.write(", ");
30213            }
30214            self.generate_expression(expr)?;
30215        }
30216        self.write(")");
30217        Ok(())
30218    }
30219
30220    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
30221        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
30222        self.write_keyword("GROUPING SETS");
30223        self.write(" (");
30224        for (i, expr) in e.expressions.iter().enumerate() {
30225            if i > 0 {
30226                self.write(", ");
30227            }
30228            self.generate_expression(expr)?;
30229        }
30230        self.write(")");
30231        Ok(())
30232    }
30233
30234    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
30235        // HASH_AGG(this, expressions...)
30236        self.write_keyword("HASH_AGG");
30237        self.write("(");
30238        self.generate_expression(&e.this)?;
30239        for expr in &e.expressions {
30240            self.write(", ");
30241            self.generate_expression(expr)?;
30242        }
30243        self.write(")");
30244        Ok(())
30245    }
30246
30247    fn generate_having(&mut self, e: &Having) -> Result<()> {
30248        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
30249        self.write_keyword("HAVING");
30250        self.write_space();
30251        self.generate_expression(&e.this)?;
30252        Ok(())
30253    }
30254
30255    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
30256        // Python: this HAVING MAX|MIN expression
30257        self.generate_expression(&e.this)?;
30258        self.write_space();
30259        self.write_keyword("HAVING");
30260        self.write_space();
30261        if e.max.is_some() {
30262            self.write_keyword("MAX");
30263        } else {
30264            self.write_keyword("MIN");
30265        }
30266        self.write_space();
30267        self.generate_expression(&e.expression)?;
30268        Ok(())
30269    }
30270
30271    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
30272        use crate::dialects::DialectType;
30273        // DuckDB: convert dollar-tagged strings to single-quoted
30274        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
30275            // Extract the string content and output as single-quoted
30276            if let Expression::Literal(ref lit) = *e.this {
30277                if let Literal::String(ref s) = lit.as_ref() {
30278                    return self.generate_string_literal(s);
30279                }
30280            }
30281        }
30282        // PostgreSQL: preserve dollar-quoting
30283        if matches!(
30284            self.config.dialect,
30285            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
30286        ) {
30287            self.write("$");
30288            if let Some(tag) = &e.tag {
30289                self.generate_expression(tag)?;
30290            }
30291            self.write("$");
30292            self.generate_expression(&e.this)?;
30293            self.write("$");
30294            if let Some(tag) = &e.tag {
30295                self.generate_expression(tag)?;
30296            }
30297            self.write("$");
30298            return Ok(());
30299        }
30300        // Default: output as dollar-tagged
30301        self.write("$");
30302        if let Some(tag) = &e.tag {
30303            self.generate_expression(tag)?;
30304        }
30305        self.write("$");
30306        self.generate_expression(&e.this)?;
30307        self.write("$");
30308        if let Some(tag) = &e.tag {
30309            self.generate_expression(tag)?;
30310        }
30311        self.write("$");
30312        Ok(())
30313    }
30314
30315    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
30316        // HEX_ENCODE(this)
30317        self.write_keyword("HEX_ENCODE");
30318        self.write("(");
30319        self.generate_expression(&e.this)?;
30320        self.write(")");
30321        Ok(())
30322    }
30323
30324    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
30325        // Python: this (kind => expression)
30326        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
30327        match e.this.as_ref() {
30328            Expression::Identifier(id) => self.write(&id.name),
30329            other => self.generate_expression(other)?,
30330        }
30331        self.write(" (");
30332        self.write(&e.kind);
30333        self.write(" => ");
30334        self.generate_expression(&e.expression)?;
30335        self.write(")");
30336        Ok(())
30337    }
30338
30339    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
30340        // HLL(this, expressions...)
30341        self.write_keyword("HLL");
30342        self.write("(");
30343        self.generate_expression(&e.this)?;
30344        for expr in &e.expressions {
30345            self.write(", ");
30346            self.generate_expression(expr)?;
30347        }
30348        self.write(")");
30349        Ok(())
30350    }
30351
30352    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
30353        // Python: IN|OUT|IN OUT
30354        if e.input_.is_some() && e.output.is_some() {
30355            self.write_keyword("IN OUT");
30356        } else if e.input_.is_some() {
30357            self.write_keyword("IN");
30358        } else if e.output.is_some() {
30359            self.write_keyword("OUT");
30360        }
30361        Ok(())
30362    }
30363
30364    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
30365        // Python: INCLUDE this [column_def] [AS alias]
30366        self.write_keyword("INCLUDE");
30367        self.write_space();
30368        self.generate_expression(&e.this)?;
30369        if let Some(column_def) = &e.column_def {
30370            self.write_space();
30371            self.generate_expression(column_def)?;
30372        }
30373        if let Some(alias) = &e.alias {
30374            self.write_space();
30375            self.write_keyword("AS");
30376            self.write_space();
30377            self.write(alias);
30378        }
30379        Ok(())
30380    }
30381
30382    fn generate_index(&mut self, e: &Index) -> Result<()> {
30383        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
30384        if e.unique {
30385            self.write_keyword("UNIQUE");
30386            self.write_space();
30387        }
30388        if e.primary.is_some() {
30389            self.write_keyword("PRIMARY");
30390            self.write_space();
30391        }
30392        if e.amp.is_some() {
30393            self.write_keyword("AMP");
30394            self.write_space();
30395        }
30396        if e.table.is_none() {
30397            self.write_keyword("INDEX");
30398            self.write_space();
30399        }
30400        if let Some(name) = &e.this {
30401            self.generate_expression(name)?;
30402            self.write_space();
30403        }
30404        if let Some(table) = &e.table {
30405            self.write_keyword("ON");
30406            self.write_space();
30407            self.generate_expression(table)?;
30408        }
30409        if !e.params.is_empty() {
30410            self.write("(");
30411            for (i, param) in e.params.iter().enumerate() {
30412                if i > 0 {
30413                    self.write(", ");
30414                }
30415                self.generate_expression(param)?;
30416            }
30417            self.write(")");
30418        }
30419        Ok(())
30420    }
30421
30422    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
30423        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
30424        if let Some(kind) = &e.kind {
30425            self.write(kind);
30426            self.write_space();
30427        }
30428        self.write_keyword("INDEX");
30429        if let Some(this) = &e.this {
30430            self.write_space();
30431            self.generate_expression(this)?;
30432        }
30433        if let Some(index_type) = &e.index_type {
30434            self.write_space();
30435            self.write_keyword("USING");
30436            self.write_space();
30437            self.generate_expression(index_type)?;
30438        }
30439        if !e.expressions.is_empty() {
30440            self.write(" (");
30441            for (i, expr) in e.expressions.iter().enumerate() {
30442                if i > 0 {
30443                    self.write(", ");
30444                }
30445                self.generate_expression(expr)?;
30446            }
30447            self.write(")");
30448        }
30449        for opt in &e.options {
30450            self.write_space();
30451            self.generate_expression(opt)?;
30452        }
30453        Ok(())
30454    }
30455
30456    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
30457        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
30458        if let Some(key_block_size) = &e.key_block_size {
30459            self.write_keyword("KEY_BLOCK_SIZE");
30460            self.write(" = ");
30461            self.generate_expression(key_block_size)?;
30462        } else if let Some(using) = &e.using {
30463            self.write_keyword("USING");
30464            self.write_space();
30465            self.generate_expression(using)?;
30466        } else if let Some(parser) = &e.parser {
30467            self.write_keyword("WITH PARSER");
30468            self.write_space();
30469            self.generate_expression(parser)?;
30470        } else if let Some(comment) = &e.comment {
30471            self.write_keyword("COMMENT");
30472            self.write_space();
30473            self.generate_expression(comment)?;
30474        } else if let Some(visible) = &e.visible {
30475            self.generate_expression(visible)?;
30476        } else if let Some(engine_attr) = &e.engine_attr {
30477            self.write_keyword("ENGINE_ATTRIBUTE");
30478            self.write(" = ");
30479            self.generate_expression(engine_attr)?;
30480        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
30481            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
30482            self.write(" = ");
30483            self.generate_expression(secondary_engine_attr)?;
30484        }
30485        Ok(())
30486    }
30487
30488    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
30489        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
30490        if let Some(using) = &e.using {
30491            self.write_keyword("USING");
30492            self.write_space();
30493            self.generate_expression(using)?;
30494        }
30495        if !e.columns.is_empty() {
30496            self.write("(");
30497            for (i, col) in e.columns.iter().enumerate() {
30498                if i > 0 {
30499                    self.write(", ");
30500                }
30501                self.generate_expression(col)?;
30502            }
30503            self.write(")");
30504        }
30505        if let Some(partition_by) = &e.partition_by {
30506            self.write_space();
30507            self.write_keyword("PARTITION BY");
30508            self.write_space();
30509            self.generate_expression(partition_by)?;
30510        }
30511        if let Some(where_) = &e.where_ {
30512            self.write_space();
30513            self.generate_expression(where_)?;
30514        }
30515        if let Some(include) = &e.include {
30516            self.write_space();
30517            self.write_keyword("INCLUDE");
30518            self.write(" (");
30519            self.generate_expression(include)?;
30520            self.write(")");
30521        }
30522        if let Some(with_storage) = &e.with_storage {
30523            self.write_space();
30524            self.write_keyword("WITH");
30525            self.write(" (");
30526            self.generate_expression(with_storage)?;
30527            self.write(")");
30528        }
30529        if let Some(tablespace) = &e.tablespace {
30530            self.write_space();
30531            self.write_keyword("USING INDEX TABLESPACE");
30532            self.write_space();
30533            self.generate_expression(tablespace)?;
30534        }
30535        Ok(())
30536    }
30537
30538    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
30539        // Python: this INDEX [FOR target] (expressions)
30540        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
30541        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
30542        if let Expression::Identifier(id) = &*e.this {
30543            self.write_keyword(&id.name);
30544        } else {
30545            self.generate_expression(&e.this)?;
30546        }
30547        self.write_space();
30548        self.write_keyword("INDEX");
30549        if let Some(target) = &e.target {
30550            self.write_space();
30551            self.write_keyword("FOR");
30552            self.write_space();
30553            if let Expression::Identifier(id) = &**target {
30554                self.write_keyword(&id.name);
30555            } else {
30556                self.generate_expression(target)?;
30557            }
30558        }
30559        // Always output parentheses (even if empty, e.g. USE INDEX ())
30560        self.write(" (");
30561        for (i, expr) in e.expressions.iter().enumerate() {
30562            if i > 0 {
30563                self.write(", ");
30564            }
30565            self.generate_expression(expr)?;
30566        }
30567        self.write(")");
30568        Ok(())
30569    }
30570
30571    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
30572        // INHERITS (table1, table2, ...)
30573        self.write_keyword("INHERITS");
30574        self.write(" (");
30575        for (i, expr) in e.expressions.iter().enumerate() {
30576            if i > 0 {
30577                self.write(", ");
30578            }
30579            self.generate_expression(expr)?;
30580        }
30581        self.write(")");
30582        Ok(())
30583    }
30584
30585    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
30586        // INPUT(model)
30587        self.write_keyword("INPUT");
30588        self.write("(");
30589        self.generate_expression(&e.this)?;
30590        self.write(")");
30591        Ok(())
30592    }
30593
30594    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
30595        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
30596        if let Some(input_format) = &e.input_format {
30597            self.write_keyword("INPUTFORMAT");
30598            self.write_space();
30599            self.generate_expression(input_format)?;
30600        }
30601        if let Some(output_format) = &e.output_format {
30602            if e.input_format.is_some() {
30603                self.write(" ");
30604            }
30605            self.write_keyword("OUTPUTFORMAT");
30606            self.write_space();
30607            self.generate_expression(output_format)?;
30608        }
30609        Ok(())
30610    }
30611
30612    fn generate_install(&mut self, e: &Install) -> Result<()> {
30613        // [FORCE] INSTALL extension [FROM source]
30614        if e.force.is_some() {
30615            self.write_keyword("FORCE");
30616            self.write_space();
30617        }
30618        self.write_keyword("INSTALL");
30619        self.write_space();
30620        self.generate_expression(&e.this)?;
30621        if let Some(from) = &e.from_ {
30622            self.write_space();
30623            self.write_keyword("FROM");
30624            self.write_space();
30625            self.generate_expression(from)?;
30626        }
30627        Ok(())
30628    }
30629
30630    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
30631        // INTERVAL 'expression' unit
30632        self.write_keyword("INTERVAL");
30633        self.write_space();
30634        // When a unit is specified and the expression is a number,
30635        self.generate_expression(&e.expression)?;
30636        if let Some(unit) = &e.unit {
30637            self.write_space();
30638            self.write(unit);
30639        }
30640        Ok(())
30641    }
30642
30643    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
30644        // unit TO unit (e.g., HOUR TO SECOND)
30645        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
30646        self.write_space();
30647        self.write_keyword("TO");
30648        self.write_space();
30649        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
30650        Ok(())
30651    }
30652
30653    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
30654        // INTO [TEMPORARY|UNLOGGED] table
30655        self.write_keyword("INTO");
30656        if e.temporary {
30657            self.write_keyword(" TEMPORARY");
30658        }
30659        if e.unlogged.is_some() {
30660            self.write_keyword(" UNLOGGED");
30661        }
30662        if let Some(this) = &e.this {
30663            self.write_space();
30664            self.generate_expression(this)?;
30665        }
30666        if !e.expressions.is_empty() {
30667            self.write(" (");
30668            for (i, expr) in e.expressions.iter().enumerate() {
30669                if i > 0 {
30670                    self.write(", ");
30671                }
30672                self.generate_expression(expr)?;
30673            }
30674            self.write(")");
30675        }
30676        Ok(())
30677    }
30678
30679    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
30680        // Python: this expression (e.g., _utf8 'string')
30681        self.generate_expression(&e.this)?;
30682        self.write_space();
30683        self.generate_expression(&e.expression)?;
30684        Ok(())
30685    }
30686
30687    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
30688        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
30689        self.write_keyword("WITH");
30690        if e.no.is_some() {
30691            self.write_keyword(" NO");
30692        }
30693        if e.concurrent.is_some() {
30694            self.write_keyword(" CONCURRENT");
30695        }
30696        self.write_keyword(" ISOLATED LOADING");
30697        if let Some(target) = &e.target {
30698            self.write_space();
30699            self.generate_expression(target)?;
30700        }
30701        Ok(())
30702    }
30703
30704    fn generate_json(&mut self, e: &JSON) -> Result<()> {
30705        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
30706        self.write_keyword("JSON");
30707        if let Some(this) = &e.this {
30708            self.write_space();
30709            self.generate_expression(this)?;
30710        }
30711        if let Some(with_) = &e.with_ {
30712            // Check if it's a truthy boolean
30713            if let Expression::Boolean(b) = with_.as_ref() {
30714                if b.value {
30715                    self.write_keyword(" WITH");
30716                } else {
30717                    self.write_keyword(" WITHOUT");
30718                }
30719            }
30720        }
30721        if e.unique {
30722            self.write_keyword(" UNIQUE KEYS");
30723        }
30724        Ok(())
30725    }
30726
30727    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
30728        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
30729        self.write_keyword("JSON_ARRAY");
30730        self.write("(");
30731        for (i, expr) in e.expressions.iter().enumerate() {
30732            if i > 0 {
30733                self.write(", ");
30734            }
30735            self.generate_expression(expr)?;
30736        }
30737        if let Some(null_handling) = &e.null_handling {
30738            self.write_space();
30739            self.generate_expression(null_handling)?;
30740        }
30741        if let Some(return_type) = &e.return_type {
30742            self.write_space();
30743            self.write_keyword("RETURNING");
30744            self.write_space();
30745            self.generate_expression(return_type)?;
30746        }
30747        if e.strict.is_some() {
30748            self.write_space();
30749            self.write_keyword("STRICT");
30750        }
30751        self.write(")");
30752        Ok(())
30753    }
30754
30755    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
30756        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
30757        self.write_keyword("JSON_ARRAYAGG");
30758        self.write("(");
30759        self.generate_expression(&e.this)?;
30760        if let Some(order) = &e.order {
30761            self.write_space();
30762            // Order is stored as an OrderBy expression
30763            if let Expression::OrderBy(ob) = order.as_ref() {
30764                self.write_keyword("ORDER BY");
30765                self.write_space();
30766                for (i, ord) in ob.expressions.iter().enumerate() {
30767                    if i > 0 {
30768                        self.write(", ");
30769                    }
30770                    self.generate_ordered(ord)?;
30771                }
30772            } else {
30773                // Fallback: generate the expression directly
30774                self.generate_expression(order)?;
30775            }
30776        }
30777        if let Some(null_handling) = &e.null_handling {
30778            self.write_space();
30779            self.generate_expression(null_handling)?;
30780        }
30781        if let Some(return_type) = &e.return_type {
30782            self.write_space();
30783            self.write_keyword("RETURNING");
30784            self.write_space();
30785            self.generate_expression(return_type)?;
30786        }
30787        if e.strict.is_some() {
30788            self.write_space();
30789            self.write_keyword("STRICT");
30790        }
30791        self.write(")");
30792        Ok(())
30793    }
30794
30795    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
30796        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
30797        self.write_keyword("JSON_OBJECTAGG");
30798        self.write("(");
30799        for (i, expr) in e.expressions.iter().enumerate() {
30800            if i > 0 {
30801                self.write(", ");
30802            }
30803            self.generate_expression(expr)?;
30804        }
30805        if let Some(null_handling) = &e.null_handling {
30806            self.write_space();
30807            self.generate_expression(null_handling)?;
30808        }
30809        if let Some(unique_keys) = &e.unique_keys {
30810            self.write_space();
30811            if let Expression::Boolean(b) = unique_keys.as_ref() {
30812                if b.value {
30813                    self.write_keyword("WITH UNIQUE KEYS");
30814                } else {
30815                    self.write_keyword("WITHOUT UNIQUE KEYS");
30816                }
30817            }
30818        }
30819        if let Some(return_type) = &e.return_type {
30820            self.write_space();
30821            self.write_keyword("RETURNING");
30822            self.write_space();
30823            self.generate_expression(return_type)?;
30824        }
30825        self.write(")");
30826        Ok(())
30827    }
30828
30829    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
30830        // JSON_ARRAY_APPEND(this, path, value, ...)
30831        self.write_keyword("JSON_ARRAY_APPEND");
30832        self.write("(");
30833        self.generate_expression(&e.this)?;
30834        for expr in &e.expressions {
30835            self.write(", ");
30836            self.generate_expression(expr)?;
30837        }
30838        self.write(")");
30839        Ok(())
30840    }
30841
30842    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
30843        // JSON_ARRAY_CONTAINS(this, expression)
30844        self.write_keyword("JSON_ARRAY_CONTAINS");
30845        self.write("(");
30846        self.generate_expression(&e.this)?;
30847        self.write(", ");
30848        self.generate_expression(&e.expression)?;
30849        self.write(")");
30850        Ok(())
30851    }
30852
30853    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
30854        // JSON_ARRAY_INSERT(this, path, value, ...)
30855        self.write_keyword("JSON_ARRAY_INSERT");
30856        self.write("(");
30857        self.generate_expression(&e.this)?;
30858        for expr in &e.expressions {
30859            self.write(", ");
30860            self.generate_expression(expr)?;
30861        }
30862        self.write(")");
30863        Ok(())
30864    }
30865
30866    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
30867        // JSONB_EXISTS(this, path)
30868        self.write_keyword("JSONB_EXISTS");
30869        self.write("(");
30870        self.generate_expression(&e.this)?;
30871        if let Some(path) = &e.path {
30872            self.write(", ");
30873            self.generate_expression(path)?;
30874        }
30875        self.write(")");
30876        Ok(())
30877    }
30878
30879    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
30880        // JSONB_EXTRACT_SCALAR(this, expression)
30881        self.write_keyword("JSONB_EXTRACT_SCALAR");
30882        self.write("(");
30883        self.generate_expression(&e.this)?;
30884        self.write(", ");
30885        self.generate_expression(&e.expression)?;
30886        self.write(")");
30887        Ok(())
30888    }
30889
30890    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
30891        // JSONB_OBJECT_AGG(this, expression)
30892        self.write_keyword("JSONB_OBJECT_AGG");
30893        self.write("(");
30894        self.generate_expression(&e.this)?;
30895        self.write(", ");
30896        self.generate_expression(&e.expression)?;
30897        self.write(")");
30898        Ok(())
30899    }
30900
30901    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
30902        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
30903        if let Some(nested_schema) = &e.nested_schema {
30904            self.write_keyword("NESTED");
30905            if let Some(path) = &e.path {
30906                self.write_space();
30907                self.write_keyword("PATH");
30908                self.write_space();
30909                self.generate_expression(path)?;
30910            }
30911            self.write_space();
30912            self.generate_expression(nested_schema)?;
30913        } else {
30914            if let Some(this) = &e.this {
30915                self.generate_expression(this)?;
30916            }
30917            if let Some(kind) = &e.kind {
30918                self.write_space();
30919                self.write(kind);
30920            }
30921            if e.format_json {
30922                self.write_space();
30923                self.write_keyword("FORMAT JSON");
30924            }
30925            if let Some(path) = &e.path {
30926                self.write_space();
30927                self.write_keyword("PATH");
30928                self.write_space();
30929                self.generate_expression(path)?;
30930            }
30931            if e.ordinality.is_some() {
30932                self.write_keyword(" FOR ORDINALITY");
30933            }
30934        }
30935        Ok(())
30936    }
30937
30938    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
30939        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
30940        self.write_keyword("JSON_EXISTS");
30941        self.write("(");
30942        self.generate_expression(&e.this)?;
30943        if let Some(path) = &e.path {
30944            self.write(", ");
30945            self.generate_expression(path)?;
30946        }
30947        if let Some(passing) = &e.passing {
30948            self.write_space();
30949            self.write_keyword("PASSING");
30950            self.write_space();
30951            self.generate_expression(passing)?;
30952        }
30953        if let Some(on_condition) = &e.on_condition {
30954            self.write_space();
30955            self.generate_expression(on_condition)?;
30956        }
30957        self.write(")");
30958        Ok(())
30959    }
30960
30961    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
30962        self.generate_expression(&e.this)?;
30963        self.write(".:");
30964        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
30965        // wrap the entire type string in double quotes.
30966        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
30967        if Self::data_type_has_nested_expressions(&e.to) {
30968            // Generate the data type to a temporary string buffer, then wrap in quotes
30969            let saved = std::mem::take(&mut self.output);
30970            self.generate_data_type(&e.to)?;
30971            let type_sql = std::mem::replace(&mut self.output, saved);
30972            self.write("\"");
30973            self.write(&type_sql);
30974            self.write("\"");
30975        } else {
30976            self.generate_data_type(&e.to)?;
30977        }
30978        Ok(())
30979    }
30980
30981    /// Check if a DataType has nested type expressions (sub-types).
30982    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
30983    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
30984        matches!(
30985            dt,
30986            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
30987        )
30988    }
30989
30990    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
30991        // JSON_EXTRACT_ARRAY(this, expression)
30992        self.write_keyword("JSON_EXTRACT_ARRAY");
30993        self.write("(");
30994        self.generate_expression(&e.this)?;
30995        if let Some(expr) = &e.expression {
30996            self.write(", ");
30997            self.generate_expression(expr)?;
30998        }
30999        self.write(")");
31000        Ok(())
31001    }
31002
31003    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
31004        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
31005        if let Some(option) = &e.option {
31006            self.generate_expression(option)?;
31007            self.write_space();
31008        }
31009        self.write_keyword("QUOTES");
31010        if e.scalar.is_some() {
31011            self.write_keyword(" SCALAR_ONLY");
31012        }
31013        Ok(())
31014    }
31015
31016    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
31017        // JSON_EXTRACT_SCALAR(this, expression)
31018        self.write_keyword("JSON_EXTRACT_SCALAR");
31019        self.write("(");
31020        self.generate_expression(&e.this)?;
31021        self.write(", ");
31022        self.generate_expression(&e.expression)?;
31023        self.write(")");
31024        Ok(())
31025    }
31026
31027    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
31028        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
31029        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
31030        // Otherwise output JSON_EXTRACT(this, expression)
31031        if e.variant_extract.is_some() {
31032            use crate::dialects::DialectType;
31033            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
31034                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
31035                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
31036                // bracket notation: c:["x-y"] instead of c:x-y
31037                self.generate_expression(&e.this)?;
31038                self.write(":");
31039                match e.expression.as_ref() {
31040                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
31041                        let Literal::String(s) = lit.as_ref() else {
31042                            unreachable!()
31043                        };
31044                        self.write_databricks_json_path(s);
31045                    }
31046                    _ => {
31047                        // Fallback: generate as-is (shouldn't happen in typical cases)
31048                        self.generate_expression(&e.expression)?;
31049                    }
31050                }
31051            } else {
31052                // Snowflake and others: use GET_PATH(col, 'path')
31053                self.write_keyword("GET_PATH");
31054                self.write("(");
31055                self.generate_expression(&e.this)?;
31056                self.write(", ");
31057                self.generate_expression(&e.expression)?;
31058                self.write(")");
31059            }
31060        } else {
31061            self.write_keyword("JSON_EXTRACT");
31062            self.write("(");
31063            self.generate_expression(&e.this)?;
31064            self.write(", ");
31065            self.generate_expression(&e.expression)?;
31066            for expr in &e.expressions {
31067                self.write(", ");
31068                self.generate_expression(expr)?;
31069            }
31070            self.write(")");
31071        }
31072        Ok(())
31073    }
31074
31075    /// Write a Databricks JSON colon-path, using bracket notation for keys
31076    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
31077    /// Safe identifier regex: ^[_a-zA-Z]\w*$
31078    fn write_databricks_json_path(&mut self, path: &str) {
31079        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
31080        // it was already formatted by the parser - output as-is
31081        if path.starts_with("[\"") || path.starts_with("['") {
31082            self.write(path);
31083            return;
31084        }
31085        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
31086        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
31087        // e.g., "x-y" -> ["x-y"]
31088        let mut first = true;
31089        for segment in path.split('.') {
31090            if !first {
31091                self.write(".");
31092            }
31093            first = false;
31094            // Check if there's a bracket subscript in this segment: "items[0]"
31095            if let Some(bracket_pos) = segment.find('[') {
31096                let key = &segment[..bracket_pos];
31097                let subscript = &segment[bracket_pos..];
31098                if key.is_empty() {
31099                    // Bracket notation at start of segment (e.g., already formatted)
31100                    self.write(segment);
31101                } else if Self::is_safe_json_path_key(key) {
31102                    self.write(key);
31103                    self.write(subscript);
31104                } else {
31105                    self.write("[\"");
31106                    self.write(key);
31107                    self.write("\"]");
31108                    self.write(subscript);
31109                }
31110            } else if Self::is_safe_json_path_key(segment) {
31111                self.write(segment);
31112            } else {
31113                self.write("[\"");
31114                self.write(segment);
31115                self.write("\"]");
31116            }
31117        }
31118    }
31119
31120    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
31121    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
31122    fn is_safe_json_path_key(key: &str) -> bool {
31123        if key.is_empty() {
31124            return false;
31125        }
31126        let mut chars = key.chars();
31127        let first = chars.next().unwrap();
31128        if first != '_' && !first.is_ascii_alphabetic() {
31129            return false;
31130        }
31131        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
31132    }
31133
31134    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
31135        // Output: {expr} FORMAT JSON
31136        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
31137        if let Some(this) = &e.this {
31138            self.generate_expression(this)?;
31139            self.write_space();
31140        }
31141        self.write_keyword("FORMAT JSON");
31142        Ok(())
31143    }
31144
31145    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
31146        // key: value (for JSON objects)
31147        self.generate_expression(&e.this)?;
31148        self.write(": ");
31149        self.generate_expression(&e.expression)?;
31150        Ok(())
31151    }
31152
31153    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
31154        // JSON_KEYS(this, expression, expressions...)
31155        self.write_keyword("JSON_KEYS");
31156        self.write("(");
31157        self.generate_expression(&e.this)?;
31158        if let Some(expr) = &e.expression {
31159            self.write(", ");
31160            self.generate_expression(expr)?;
31161        }
31162        for expr in &e.expressions {
31163            self.write(", ");
31164            self.generate_expression(expr)?;
31165        }
31166        self.write(")");
31167        Ok(())
31168    }
31169
31170    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
31171        // JSON_KEYS(this, expression)
31172        self.write_keyword("JSON_KEYS");
31173        self.write("(");
31174        self.generate_expression(&e.this)?;
31175        if let Some(expr) = &e.expression {
31176            self.write(", ");
31177            self.generate_expression(expr)?;
31178        }
31179        self.write(")");
31180        Ok(())
31181    }
31182
31183    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
31184        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
31185        // The path components are concatenated without spaces
31186        let mut path_str = String::new();
31187        for expr in &e.expressions {
31188            match expr {
31189                Expression::JSONPathRoot(_) => {
31190                    path_str.push('$');
31191                }
31192                Expression::JSONPathKey(k) => {
31193                    // .key or ."key" (quote if key has special characters)
31194                    if let Expression::Literal(lit) = k.this.as_ref() {
31195                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
31196                            path_str.push('.');
31197                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
31198                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
31199                            if needs_quoting {
31200                                path_str.push('"');
31201                                path_str.push_str(s);
31202                                path_str.push('"');
31203                            } else {
31204                                path_str.push_str(s);
31205                            }
31206                        }
31207                    }
31208                }
31209                Expression::JSONPathSubscript(s) => {
31210                    // [index]
31211                    if let Expression::Literal(lit) = s.this.as_ref() {
31212                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
31213                            path_str.push('[');
31214                            path_str.push_str(n);
31215                            path_str.push(']');
31216                        }
31217                    }
31218                }
31219                _ => {
31220                    // For other path parts, try to generate them
31221                    let mut temp_gen = Self::with_arc_config(self.config.clone());
31222                    temp_gen.generate_expression(expr)?;
31223                    path_str.push_str(&temp_gen.output);
31224                }
31225            }
31226        }
31227        // Output as quoted string
31228        self.write("'");
31229        self.write(&path_str);
31230        self.write("'");
31231        Ok(())
31232    }
31233
31234    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
31235        // JSON path filter: ?(predicate)
31236        self.write("?(");
31237        self.generate_expression(&e.this)?;
31238        self.write(")");
31239        Ok(())
31240    }
31241
31242    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
31243        // JSON path key: .key or ["key"]
31244        self.write(".");
31245        self.generate_expression(&e.this)?;
31246        Ok(())
31247    }
31248
31249    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
31250        // JSON path recursive descent: ..
31251        self.write("..");
31252        if let Some(this) = &e.this {
31253            self.generate_expression(this)?;
31254        }
31255        Ok(())
31256    }
31257
31258    fn generate_json_path_root(&mut self) -> Result<()> {
31259        // JSON path root: $
31260        self.write("$");
31261        Ok(())
31262    }
31263
31264    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
31265        // JSON path script: (expression)
31266        self.write("(");
31267        self.generate_expression(&e.this)?;
31268        self.write(")");
31269        Ok(())
31270    }
31271
31272    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
31273        // JSON path selector: *
31274        self.generate_expression(&e.this)?;
31275        Ok(())
31276    }
31277
31278    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
31279        // JSON path slice: [start:end:step]
31280        self.write("[");
31281        if let Some(start) = &e.start {
31282            self.generate_expression(start)?;
31283        }
31284        self.write(":");
31285        if let Some(end) = &e.end {
31286            self.generate_expression(end)?;
31287        }
31288        if let Some(step) = &e.step {
31289            self.write(":");
31290            self.generate_expression(step)?;
31291        }
31292        self.write("]");
31293        Ok(())
31294    }
31295
31296    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
31297        // JSON path subscript: [index] or [*]
31298        self.write("[");
31299        self.generate_expression(&e.this)?;
31300        self.write("]");
31301        Ok(())
31302    }
31303
31304    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
31305        // JSON path union: [key1, key2, ...]
31306        self.write("[");
31307        for (i, expr) in e.expressions.iter().enumerate() {
31308            if i > 0 {
31309                self.write(", ");
31310            }
31311            self.generate_expression(expr)?;
31312        }
31313        self.write("]");
31314        Ok(())
31315    }
31316
31317    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
31318        // JSON_REMOVE(this, path1, path2, ...)
31319        self.write_keyword("JSON_REMOVE");
31320        self.write("(");
31321        self.generate_expression(&e.this)?;
31322        for expr in &e.expressions {
31323            self.write(", ");
31324            self.generate_expression(expr)?;
31325        }
31326        self.write(")");
31327        Ok(())
31328    }
31329
31330    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
31331        // COLUMNS(col1 type, col2 type, ...)
31332        // When pretty printing and content is too wide, format with each column on a separate line
31333        self.write_keyword("COLUMNS");
31334        self.write("(");
31335
31336        if self.config.pretty && !e.expressions.is_empty() {
31337            // First, generate all expressions into strings to check width
31338            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
31339            for expr in &e.expressions {
31340                let mut temp_gen = Generator::with_arc_config(self.config.clone());
31341                temp_gen.generate_expression(expr)?;
31342                expr_strings.push(temp_gen.output);
31343            }
31344
31345            // Check if total width exceeds max_text_width
31346            if self.too_wide(&expr_strings) {
31347                // Pretty print: each column on its own line
31348                self.write_newline();
31349                self.indent_level += 1;
31350                for (i, expr_str) in expr_strings.iter().enumerate() {
31351                    if i > 0 {
31352                        self.write(",");
31353                        self.write_newline();
31354                    }
31355                    self.write_indent();
31356                    self.write(expr_str);
31357                }
31358                self.write_newline();
31359                self.indent_level -= 1;
31360                self.write_indent();
31361            } else {
31362                // Compact: all on one line
31363                for (i, expr_str) in expr_strings.iter().enumerate() {
31364                    if i > 0 {
31365                        self.write(", ");
31366                    }
31367                    self.write(expr_str);
31368                }
31369            }
31370        } else {
31371            // Non-pretty mode: compact format
31372            for (i, expr) in e.expressions.iter().enumerate() {
31373                if i > 0 {
31374                    self.write(", ");
31375                }
31376                self.generate_expression(expr)?;
31377            }
31378        }
31379        self.write(")");
31380        Ok(())
31381    }
31382
31383    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
31384        // JSON_SET(this, path, value, ...)
31385        self.write_keyword("JSON_SET");
31386        self.write("(");
31387        self.generate_expression(&e.this)?;
31388        for expr in &e.expressions {
31389            self.write(", ");
31390            self.generate_expression(expr)?;
31391        }
31392        self.write(")");
31393        Ok(())
31394    }
31395
31396    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
31397        // JSON_STRIP_NULLS(this, expression)
31398        self.write_keyword("JSON_STRIP_NULLS");
31399        self.write("(");
31400        self.generate_expression(&e.this)?;
31401        if let Some(expr) = &e.expression {
31402            self.write(", ");
31403            self.generate_expression(expr)?;
31404        }
31405        self.write(")");
31406        Ok(())
31407    }
31408
31409    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
31410        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
31411        self.write_keyword("JSON_TABLE");
31412        self.write("(");
31413        self.generate_expression(&e.this)?;
31414        if let Some(path) = &e.path {
31415            self.write(", ");
31416            self.generate_expression(path)?;
31417        }
31418        if let Some(error_handling) = &e.error_handling {
31419            self.write_space();
31420            self.generate_expression(error_handling)?;
31421        }
31422        if let Some(empty_handling) = &e.empty_handling {
31423            self.write_space();
31424            self.generate_expression(empty_handling)?;
31425        }
31426        if let Some(schema) = &e.schema {
31427            self.write_space();
31428            self.generate_expression(schema)?;
31429        }
31430        self.write(")");
31431        Ok(())
31432    }
31433
31434    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
31435        // JSON_TYPE(this)
31436        self.write_keyword("JSON_TYPE");
31437        self.write("(");
31438        self.generate_expression(&e.this)?;
31439        self.write(")");
31440        Ok(())
31441    }
31442
31443    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
31444        // JSON_VALUE(this, path RETURNING type ON condition)
31445        self.write_keyword("JSON_VALUE");
31446        self.write("(");
31447        self.generate_expression(&e.this)?;
31448        if let Some(path) = &e.path {
31449            self.write(", ");
31450            self.generate_expression(path)?;
31451        }
31452        if let Some(returning) = &e.returning {
31453            self.write_space();
31454            self.write_keyword("RETURNING");
31455            self.write_space();
31456            self.generate_expression(returning)?;
31457        }
31458        if let Some(on_condition) = &e.on_condition {
31459            self.write_space();
31460            self.generate_expression(on_condition)?;
31461        }
31462        self.write(")");
31463        Ok(())
31464    }
31465
31466    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
31467        // JSON_VALUE_ARRAY(this)
31468        self.write_keyword("JSON_VALUE_ARRAY");
31469        self.write("(");
31470        self.generate_expression(&e.this)?;
31471        self.write(")");
31472        Ok(())
31473    }
31474
31475    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
31476        // JAROWINKLER_SIMILARITY(str1, str2)
31477        self.write_keyword("JAROWINKLER_SIMILARITY");
31478        self.write("(");
31479        self.generate_expression(&e.this)?;
31480        self.write(", ");
31481        self.generate_expression(&e.expression)?;
31482        self.write(")");
31483        Ok(())
31484    }
31485
31486    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
31487        // Python: this(expressions)
31488        self.generate_expression(&e.this)?;
31489        self.write("(");
31490        for (i, expr) in e.expressions.iter().enumerate() {
31491            if i > 0 {
31492                self.write(", ");
31493            }
31494            self.generate_expression(expr)?;
31495        }
31496        self.write(")");
31497        Ok(())
31498    }
31499
31500    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
31501        // Python: {no}{local}{dual}{before}{after}JOURNAL
31502        if e.no.is_some() {
31503            self.write_keyword("NO ");
31504        }
31505        if let Some(local) = &e.local {
31506            self.generate_expression(local)?;
31507            self.write_space();
31508        }
31509        if e.dual.is_some() {
31510            self.write_keyword("DUAL ");
31511        }
31512        if e.before.is_some() {
31513            self.write_keyword("BEFORE ");
31514        }
31515        if e.after.is_some() {
31516            self.write_keyword("AFTER ");
31517        }
31518        self.write_keyword("JOURNAL");
31519        Ok(())
31520    }
31521
31522    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
31523        // LANGUAGE language_name
31524        self.write_keyword("LANGUAGE");
31525        self.write_space();
31526        self.generate_expression(&e.this)?;
31527        Ok(())
31528    }
31529
31530    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
31531        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
31532        if e.view.is_some() {
31533            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
31534            self.write_keyword("LATERAL VIEW");
31535            if e.outer.is_some() {
31536                self.write_space();
31537                self.write_keyword("OUTER");
31538            }
31539            self.write_space();
31540            self.generate_expression(&e.this)?;
31541            if let Some(alias) = &e.alias {
31542                self.write_space();
31543                self.write(alias);
31544            }
31545        } else {
31546            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
31547            self.write_keyword("LATERAL");
31548            self.write_space();
31549            self.generate_expression(&e.this)?;
31550            if e.ordinality.is_some() {
31551                self.write_space();
31552                self.write_keyword("WITH ORDINALITY");
31553            }
31554            if let Some(alias) = &e.alias {
31555                self.write_space();
31556                self.write_keyword("AS");
31557                self.write_space();
31558                self.write(alias);
31559                if !e.column_aliases.is_empty() {
31560                    self.write("(");
31561                    for (i, col) in e.column_aliases.iter().enumerate() {
31562                        if i > 0 {
31563                            self.write(", ");
31564                        }
31565                        self.write(col);
31566                    }
31567                    self.write(")");
31568                }
31569            }
31570        }
31571        Ok(())
31572    }
31573
31574    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
31575        // Python: LIKE this [options]
31576        self.write_keyword("LIKE");
31577        self.write_space();
31578        self.generate_expression(&e.this)?;
31579        for expr in &e.expressions {
31580            self.write_space();
31581            self.generate_expression(expr)?;
31582        }
31583        Ok(())
31584    }
31585
31586    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
31587        self.write_keyword("LIMIT");
31588        self.write_space();
31589        self.write_limit_expr(&e.this)?;
31590        if e.percent {
31591            self.write_space();
31592            self.write_keyword("PERCENT");
31593        }
31594        // Emit any comments that were captured from before the LIMIT keyword
31595        for comment in &e.comments {
31596            self.write(" ");
31597            self.write_formatted_comment(comment);
31598        }
31599        Ok(())
31600    }
31601
31602    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
31603        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
31604        if e.percent.is_some() {
31605            self.write_keyword(" PERCENT");
31606        }
31607        if e.rows.is_some() {
31608            self.write_keyword(" ROWS");
31609        }
31610        if e.with_ties.is_some() {
31611            self.write_keyword(" WITH TIES");
31612        } else if e.rows.is_some() {
31613            self.write_keyword(" ONLY");
31614        }
31615        Ok(())
31616    }
31617
31618    fn generate_list(&mut self, e: &List) -> Result<()> {
31619        use crate::dialects::DialectType;
31620        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
31621
31622        // Check if this is a subquery-based list (LIST(SELECT ...))
31623        if e.expressions.len() == 1 {
31624            if let Expression::Select(_) = &e.expressions[0] {
31625                self.write_keyword("LIST");
31626                self.write("(");
31627                self.generate_expression(&e.expressions[0])?;
31628                self.write(")");
31629                return Ok(());
31630            }
31631        }
31632
31633        // For Materialize, output as LIST[expr, expr, ...]
31634        if is_materialize {
31635            self.write_keyword("LIST");
31636            self.write("[");
31637            for (i, expr) in e.expressions.iter().enumerate() {
31638                if i > 0 {
31639                    self.write(", ");
31640                }
31641                self.generate_expression(expr)?;
31642            }
31643            self.write("]");
31644        } else {
31645            // For other dialects, output as LIST(expr, expr, ...)
31646            self.write_keyword("LIST");
31647            self.write("(");
31648            for (i, expr) in e.expressions.iter().enumerate() {
31649                if i > 0 {
31650                    self.write(", ");
31651                }
31652                self.generate_expression(expr)?;
31653            }
31654            self.write(")");
31655        }
31656        Ok(())
31657    }
31658
31659    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
31660        // Check if this is a subquery-based map (MAP(SELECT ...))
31661        if let Expression::Select(_) = &*e.this {
31662            self.write_keyword("MAP");
31663            self.write("(");
31664            self.generate_expression(&e.this)?;
31665            self.write(")");
31666            return Ok(());
31667        }
31668
31669        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
31670
31671        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
31672        self.write_keyword("MAP");
31673        if is_duckdb {
31674            self.write(" {");
31675        } else {
31676            self.write("[");
31677        }
31678        if let Expression::Struct(s) = &*e.this {
31679            for (i, (_, expr)) in s.fields.iter().enumerate() {
31680                if i > 0 {
31681                    self.write(", ");
31682                }
31683                if let Expression::PropertyEQ(op) = expr {
31684                    self.generate_expression(&op.left)?;
31685                    if is_duckdb {
31686                        self.write(": ");
31687                    } else {
31688                        self.write(" => ");
31689                    }
31690                    self.generate_expression(&op.right)?;
31691                } else {
31692                    self.generate_expression(expr)?;
31693                }
31694            }
31695        }
31696        if is_duckdb {
31697            self.write("}");
31698        } else {
31699            self.write("]");
31700        }
31701        Ok(())
31702    }
31703
31704    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
31705        // Python: LOCALTIME or LOCALTIME(precision)
31706        self.write_keyword("LOCALTIME");
31707        if let Some(precision) = &e.this {
31708            self.write("(");
31709            self.generate_expression(precision)?;
31710            self.write(")");
31711        }
31712        Ok(())
31713    }
31714
31715    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
31716        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
31717        self.write_keyword("LOCALTIMESTAMP");
31718        if let Some(precision) = &e.this {
31719            self.write("(");
31720            self.generate_expression(precision)?;
31721            self.write(")");
31722        }
31723        Ok(())
31724    }
31725
31726    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
31727        // LOCATION 'path'
31728        self.write_keyword("LOCATION");
31729        self.write_space();
31730        self.generate_expression(&e.this)?;
31731        Ok(())
31732    }
31733
31734    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
31735        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
31736        if e.update.is_some() {
31737            if e.key.is_some() {
31738                self.write_keyword("FOR NO KEY UPDATE");
31739            } else {
31740                self.write_keyword("FOR UPDATE");
31741            }
31742        } else {
31743            if e.key.is_some() {
31744                self.write_keyword("FOR KEY SHARE");
31745            } else {
31746                self.write_keyword("FOR SHARE");
31747            }
31748        }
31749        if !e.expressions.is_empty() {
31750            self.write_keyword(" OF ");
31751            for (i, expr) in e.expressions.iter().enumerate() {
31752                if i > 0 {
31753                    self.write(", ");
31754                }
31755                self.generate_expression(expr)?;
31756            }
31757        }
31758        // Handle wait option following Python sqlglot convention:
31759        // - Boolean(true) -> NOWAIT
31760        // - Boolean(false) -> SKIP LOCKED
31761        // - Literal (number) -> WAIT n
31762        if let Some(wait) = &e.wait {
31763            match wait.as_ref() {
31764                Expression::Boolean(b) => {
31765                    if b.value {
31766                        self.write_keyword(" NOWAIT");
31767                    } else {
31768                        self.write_keyword(" SKIP LOCKED");
31769                    }
31770                }
31771                _ => {
31772                    // It's a literal (number), output WAIT n
31773                    self.write_keyword(" WAIT ");
31774                    self.generate_expression(wait)?;
31775                }
31776            }
31777        }
31778        Ok(())
31779    }
31780
31781    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
31782        // LOCK property
31783        self.write_keyword("LOCK");
31784        self.write_space();
31785        self.generate_expression(&e.this)?;
31786        Ok(())
31787    }
31788
31789    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
31790        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
31791        self.write_keyword("LOCKING");
31792        self.write_space();
31793        self.write(&e.kind);
31794        if let Some(this) = &e.this {
31795            self.write_space();
31796            self.generate_expression(this)?;
31797        }
31798        if let Some(for_or_in) = &e.for_or_in {
31799            self.write_space();
31800            self.generate_expression(for_or_in)?;
31801        }
31802        if let Some(lock_type) = &e.lock_type {
31803            self.write_space();
31804            self.generate_expression(lock_type)?;
31805        }
31806        if e.override_.is_some() {
31807            self.write_keyword(" OVERRIDE");
31808        }
31809        Ok(())
31810    }
31811
31812    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
31813        // this expression
31814        self.generate_expression(&e.this)?;
31815        self.write_space();
31816        self.generate_expression(&e.expression)?;
31817        Ok(())
31818    }
31819
31820    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
31821        // [NO] LOG
31822        if e.no.is_some() {
31823            self.write_keyword("NO ");
31824        }
31825        self.write_keyword("LOG");
31826        Ok(())
31827    }
31828
31829    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
31830        // MD5(this, expressions...)
31831        self.write_keyword("MD5");
31832        self.write("(");
31833        self.generate_expression(&e.this)?;
31834        for expr in &e.expressions {
31835            self.write(", ");
31836            self.generate_expression(expr)?;
31837        }
31838        self.write(")");
31839        Ok(())
31840    }
31841
31842    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
31843        // ML.FORECAST(model, [params])
31844        self.write_keyword("ML.FORECAST");
31845        self.write("(");
31846        self.generate_expression(&e.this)?;
31847        if let Some(expression) = &e.expression {
31848            self.write(", ");
31849            self.generate_expression(expression)?;
31850        }
31851        if let Some(params) = &e.params_struct {
31852            self.write(", ");
31853            self.generate_expression(params)?;
31854        }
31855        self.write(")");
31856        Ok(())
31857    }
31858
31859    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
31860        // ML.TRANSLATE(model, input, [params])
31861        self.write_keyword("ML.TRANSLATE");
31862        self.write("(");
31863        self.generate_expression(&e.this)?;
31864        self.write(", ");
31865        self.generate_expression(&e.expression)?;
31866        if let Some(params) = &e.params_struct {
31867            self.write(", ");
31868            self.generate_expression(params)?;
31869        }
31870        self.write(")");
31871        Ok(())
31872    }
31873
31874    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
31875        // MAKE_INTERVAL(years => x, months => y, ...)
31876        self.write_keyword("MAKE_INTERVAL");
31877        self.write("(");
31878        let mut first = true;
31879        if let Some(year) = &e.year {
31880            self.write("years => ");
31881            self.generate_expression(year)?;
31882            first = false;
31883        }
31884        if let Some(month) = &e.month {
31885            if !first {
31886                self.write(", ");
31887            }
31888            self.write("months => ");
31889            self.generate_expression(month)?;
31890            first = false;
31891        }
31892        if let Some(week) = &e.week {
31893            if !first {
31894                self.write(", ");
31895            }
31896            self.write("weeks => ");
31897            self.generate_expression(week)?;
31898            first = false;
31899        }
31900        if let Some(day) = &e.day {
31901            if !first {
31902                self.write(", ");
31903            }
31904            self.write("days => ");
31905            self.generate_expression(day)?;
31906            first = false;
31907        }
31908        if let Some(hour) = &e.hour {
31909            if !first {
31910                self.write(", ");
31911            }
31912            self.write("hours => ");
31913            self.generate_expression(hour)?;
31914            first = false;
31915        }
31916        if let Some(minute) = &e.minute {
31917            if !first {
31918                self.write(", ");
31919            }
31920            self.write("mins => ");
31921            self.generate_expression(minute)?;
31922            first = false;
31923        }
31924        if let Some(second) = &e.second {
31925            if !first {
31926                self.write(", ");
31927            }
31928            self.write("secs => ");
31929            self.generate_expression(second)?;
31930        }
31931        self.write(")");
31932        Ok(())
31933    }
31934
31935    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
31936        // MANHATTAN_DISTANCE(vector1, vector2)
31937        self.write_keyword("MANHATTAN_DISTANCE");
31938        self.write("(");
31939        self.generate_expression(&e.this)?;
31940        self.write(", ");
31941        self.generate_expression(&e.expression)?;
31942        self.write(")");
31943        Ok(())
31944    }
31945
31946    fn generate_map(&mut self, e: &Map) -> Result<()> {
31947        // MAP(key1, value1, key2, value2, ...)
31948        self.write_keyword("MAP");
31949        self.write("(");
31950        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
31951            if i > 0 {
31952                self.write(", ");
31953            }
31954            self.generate_expression(key)?;
31955            self.write(", ");
31956            self.generate_expression(value)?;
31957        }
31958        self.write(")");
31959        Ok(())
31960    }
31961
31962    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
31963        // MAP_CAT(map1, map2)
31964        self.write_keyword("MAP_CAT");
31965        self.write("(");
31966        self.generate_expression(&e.this)?;
31967        self.write(", ");
31968        self.generate_expression(&e.expression)?;
31969        self.write(")");
31970        Ok(())
31971    }
31972
31973    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
31974        // MAP_DELETE(map, key1, key2, ...)
31975        self.write_keyword("MAP_DELETE");
31976        self.write("(");
31977        self.generate_expression(&e.this)?;
31978        for expr in &e.expressions {
31979            self.write(", ");
31980            self.generate_expression(expr)?;
31981        }
31982        self.write(")");
31983        Ok(())
31984    }
31985
31986    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
31987        // MAP_INSERT(map, key, value, [update_flag])
31988        self.write_keyword("MAP_INSERT");
31989        self.write("(");
31990        self.generate_expression(&e.this)?;
31991        if let Some(key) = &e.key {
31992            self.write(", ");
31993            self.generate_expression(key)?;
31994        }
31995        if let Some(value) = &e.value {
31996            self.write(", ");
31997            self.generate_expression(value)?;
31998        }
31999        if let Some(update_flag) = &e.update_flag {
32000            self.write(", ");
32001            self.generate_expression(update_flag)?;
32002        }
32003        self.write(")");
32004        Ok(())
32005    }
32006
32007    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
32008        // MAP_PICK(map, key1, key2, ...)
32009        self.write_keyword("MAP_PICK");
32010        self.write("(");
32011        self.generate_expression(&e.this)?;
32012        for expr in &e.expressions {
32013            self.write(", ");
32014            self.generate_expression(expr)?;
32015        }
32016        self.write(")");
32017        Ok(())
32018    }
32019
32020    fn generate_masking_policy_column_constraint(
32021        &mut self,
32022        e: &MaskingPolicyColumnConstraint,
32023    ) -> Result<()> {
32024        // Python: MASKING POLICY name [USING (cols)]
32025        self.write_keyword("MASKING POLICY");
32026        self.write_space();
32027        self.generate_expression(&e.this)?;
32028        if !e.expressions.is_empty() {
32029            self.write_keyword(" USING");
32030            self.write(" (");
32031            for (i, expr) in e.expressions.iter().enumerate() {
32032                if i > 0 {
32033                    self.write(", ");
32034                }
32035                self.generate_expression(expr)?;
32036            }
32037            self.write(")");
32038        }
32039        Ok(())
32040    }
32041
32042    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
32043        if matches!(
32044            self.config.dialect,
32045            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
32046        ) {
32047            if e.expressions.len() > 1 {
32048                self.write("(");
32049            }
32050            for (i, expr) in e.expressions.iter().enumerate() {
32051                if i > 0 {
32052                    self.write_keyword(" OR ");
32053                }
32054                self.generate_expression(expr)?;
32055                self.write_space();
32056                self.write("@@");
32057                self.write_space();
32058                self.generate_expression(&e.this)?;
32059            }
32060            if e.expressions.len() > 1 {
32061                self.write(")");
32062            }
32063            return Ok(());
32064        }
32065
32066        // MATCH(columns) AGAINST(expr [modifier])
32067        self.write_keyword("MATCH");
32068        self.write("(");
32069        for (i, expr) in e.expressions.iter().enumerate() {
32070            if i > 0 {
32071                self.write(", ");
32072            }
32073            self.generate_expression(expr)?;
32074        }
32075        self.write(")");
32076        self.write_keyword(" AGAINST");
32077        self.write("(");
32078        self.generate_expression(&e.this)?;
32079        if let Some(modifier) = &e.modifier {
32080            self.write_space();
32081            self.generate_expression(modifier)?;
32082        }
32083        self.write(")");
32084        Ok(())
32085    }
32086
32087    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
32088        // Python: [window_frame] this
32089        if let Some(window_frame) = &e.window_frame {
32090            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
32091            self.write_space();
32092        }
32093        self.generate_expression(&e.this)?;
32094        Ok(())
32095    }
32096
32097    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
32098        // MATERIALIZED [this]
32099        self.write_keyword("MATERIALIZED");
32100        if let Some(this) = &e.this {
32101            self.write_space();
32102            self.generate_expression(this)?;
32103        }
32104        Ok(())
32105    }
32106
32107    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
32108        // MERGE INTO target USING source ON condition WHEN ...
32109        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
32110        if let Some(with_) = &e.with_ {
32111            if let Expression::With(with_clause) = with_.as_ref() {
32112                self.generate_with(with_clause)?;
32113                self.write_space();
32114            } else {
32115                self.generate_expression(with_)?;
32116                self.write_space();
32117            }
32118        }
32119        self.write_keyword("MERGE INTO");
32120        self.write_space();
32121        if matches!(self.config.dialect, Some(crate::DialectType::Oracle)) {
32122            if let Expression::Alias(alias) = e.this.as_ref() {
32123                self.generate_expression(&alias.this)?;
32124                self.write_space();
32125                self.generate_identifier(&alias.alias)?;
32126            } else {
32127                self.generate_expression(&e.this)?;
32128            }
32129        } else {
32130            self.generate_expression(&e.this)?;
32131        }
32132
32133        // USING clause - newline before in pretty mode
32134        if self.config.pretty {
32135            self.write_newline();
32136            self.write_indent();
32137        } else {
32138            self.write_space();
32139        }
32140        self.write_keyword("USING");
32141        self.write_space();
32142        self.generate_expression(&e.using)?;
32143
32144        // ON clause - newline before in pretty mode
32145        if let Some(on) = &e.on {
32146            if self.config.pretty {
32147                self.write_newline();
32148                self.write_indent();
32149            } else {
32150                self.write_space();
32151            }
32152            self.write_keyword("ON");
32153            self.write_space();
32154            self.generate_expression(on)?;
32155        }
32156        // DuckDB USING (key_columns) clause
32157        if let Some(using_cond) = &e.using_cond {
32158            self.write_space();
32159            self.write_keyword("USING");
32160            self.write_space();
32161            self.write("(");
32162            // using_cond is a Tuple containing the column identifiers
32163            if let Expression::Tuple(tuple) = using_cond.as_ref() {
32164                for (i, col) in tuple.expressions.iter().enumerate() {
32165                    if i > 0 {
32166                        self.write(", ");
32167                    }
32168                    self.generate_expression(col)?;
32169                }
32170            } else {
32171                self.generate_expression(using_cond)?;
32172            }
32173            self.write(")");
32174        }
32175        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
32176        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
32177        if matches!(
32178            self.config.dialect,
32179            Some(crate::DialectType::PostgreSQL)
32180                | Some(crate::DialectType::Redshift)
32181                | Some(crate::DialectType::Trino)
32182                | Some(crate::DialectType::Presto)
32183                | Some(crate::DialectType::Athena)
32184        ) {
32185            let mut names = Vec::new();
32186            match e.this.as_ref() {
32187                Expression::Alias(a) => {
32188                    // e.g., "x AS z" -> strip both "x" and "z"
32189                    if let Expression::Table(t) = &a.this {
32190                        names.push(t.name.name.clone());
32191                    } else if let Expression::Identifier(id) = &a.this {
32192                        names.push(id.name.clone());
32193                    }
32194                    names.push(a.alias.name.clone());
32195                }
32196                Expression::Table(t) => {
32197                    names.push(t.name.name.clone());
32198                }
32199                Expression::Identifier(id) => {
32200                    names.push(id.name.clone());
32201                }
32202                _ => {}
32203            }
32204            self.merge_strip_qualifiers = names;
32205        }
32206
32207        // WHEN clauses - newline before each in pretty mode
32208        if let Some(whens) = &e.whens {
32209            if self.config.pretty {
32210                self.write_newline();
32211                self.write_indent();
32212            } else {
32213                self.write_space();
32214            }
32215            self.generate_expression(whens)?;
32216        }
32217
32218        // Restore merge_strip_qualifiers
32219        self.merge_strip_qualifiers = saved_merge_strip;
32220
32221        // OUTPUT/RETURNING clause - newline before in pretty mode
32222        if let Some(returning) = &e.returning {
32223            if self.config.pretty {
32224                self.write_newline();
32225                self.write_indent();
32226            } else {
32227                self.write_space();
32228            }
32229            self.generate_expression(returning)?;
32230        }
32231        Ok(())
32232    }
32233
32234    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
32235        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
32236        if e.no.is_some() {
32237            self.write_keyword("NO MERGEBLOCKRATIO");
32238        } else if e.default.is_some() {
32239            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
32240        } else {
32241            self.write_keyword("MERGEBLOCKRATIO");
32242            self.write("=");
32243            if let Some(this) = &e.this {
32244                self.generate_expression(this)?;
32245            }
32246            if e.percent.is_some() {
32247                self.write_keyword(" PERCENT");
32248            }
32249        }
32250        Ok(())
32251    }
32252
32253    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
32254        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
32255        self.write_keyword("TTL");
32256        let pretty_clickhouse = self.config.pretty
32257            && matches!(
32258                self.config.dialect,
32259                Some(crate::dialects::DialectType::ClickHouse)
32260            );
32261
32262        if pretty_clickhouse {
32263            self.write_newline();
32264            self.indent_level += 1;
32265            for (i, expr) in e.expressions.iter().enumerate() {
32266                if i > 0 {
32267                    self.write(",");
32268                    self.write_newline();
32269                }
32270                self.write_indent();
32271                self.generate_expression(expr)?;
32272            }
32273            self.indent_level -= 1;
32274        } else {
32275            self.write_space();
32276            for (i, expr) in e.expressions.iter().enumerate() {
32277                if i > 0 {
32278                    self.write(", ");
32279                }
32280                self.generate_expression(expr)?;
32281            }
32282        }
32283
32284        if let Some(where_) = &e.where_ {
32285            if pretty_clickhouse {
32286                self.write_newline();
32287                if let Expression::Where(w) = where_.as_ref() {
32288                    self.write_indent();
32289                    self.write_keyword("WHERE");
32290                    self.write_newline();
32291                    self.indent_level += 1;
32292                    self.write_indent();
32293                    self.generate_expression(&w.this)?;
32294                    self.indent_level -= 1;
32295                } else {
32296                    self.write_indent();
32297                    self.generate_expression(where_)?;
32298                }
32299            } else {
32300                self.write_space();
32301                self.generate_expression(where_)?;
32302            }
32303        }
32304        if let Some(group) = &e.group {
32305            if pretty_clickhouse {
32306                self.write_newline();
32307                if let Expression::Group(g) = group.as_ref() {
32308                    self.write_indent();
32309                    self.write_keyword("GROUP BY");
32310                    self.write_newline();
32311                    self.indent_level += 1;
32312                    for (i, expr) in g.expressions.iter().enumerate() {
32313                        if i > 0 {
32314                            self.write(",");
32315                            self.write_newline();
32316                        }
32317                        self.write_indent();
32318                        self.generate_expression(expr)?;
32319                    }
32320                    self.indent_level -= 1;
32321                } else {
32322                    self.write_indent();
32323                    self.generate_expression(group)?;
32324                }
32325            } else {
32326                self.write_space();
32327                self.generate_expression(group)?;
32328            }
32329        }
32330        if let Some(aggregates) = &e.aggregates {
32331            if pretty_clickhouse {
32332                self.write_newline();
32333                self.write_indent();
32334                self.write_keyword("SET");
32335                self.write_newline();
32336                self.indent_level += 1;
32337                if let Expression::Tuple(t) = aggregates.as_ref() {
32338                    for (i, agg) in t.expressions.iter().enumerate() {
32339                        if i > 0 {
32340                            self.write(",");
32341                            self.write_newline();
32342                        }
32343                        self.write_indent();
32344                        self.generate_expression(agg)?;
32345                    }
32346                } else {
32347                    self.write_indent();
32348                    self.generate_expression(aggregates)?;
32349                }
32350                self.indent_level -= 1;
32351            } else {
32352                self.write_space();
32353                self.write_keyword("SET");
32354                self.write_space();
32355                if let Expression::Tuple(t) = aggregates.as_ref() {
32356                    for (i, agg) in t.expressions.iter().enumerate() {
32357                        if i > 0 {
32358                            self.write(", ");
32359                        }
32360                        self.generate_expression(agg)?;
32361                    }
32362                } else {
32363                    self.generate_expression(aggregates)?;
32364                }
32365            }
32366        }
32367        Ok(())
32368    }
32369
32370    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
32371        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
32372        self.generate_expression(&e.this)?;
32373        if e.delete.is_some() {
32374            self.write_keyword(" DELETE");
32375        }
32376        if let Some(recompress) = &e.recompress {
32377            self.write_keyword(" RECOMPRESS ");
32378            self.generate_expression(recompress)?;
32379        }
32380        if let Some(to_disk) = &e.to_disk {
32381            self.write_keyword(" TO DISK ");
32382            self.generate_expression(to_disk)?;
32383        }
32384        if let Some(to_volume) = &e.to_volume {
32385            self.write_keyword(" TO VOLUME ");
32386            self.generate_expression(to_volume)?;
32387        }
32388        Ok(())
32389    }
32390
32391    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
32392        // MINHASH(this, expressions...)
32393        self.write_keyword("MINHASH");
32394        self.write("(");
32395        self.generate_expression(&e.this)?;
32396        for expr in &e.expressions {
32397            self.write(", ");
32398            self.generate_expression(expr)?;
32399        }
32400        self.write(")");
32401        Ok(())
32402    }
32403
32404    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
32405        // model!attribute - Snowflake syntax
32406        self.generate_expression(&e.this)?;
32407        self.write("!");
32408        self.generate_expression(&e.expression)?;
32409        Ok(())
32410    }
32411
32412    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
32413        // MONTHNAME(this)
32414        self.write_keyword("MONTHNAME");
32415        self.write("(");
32416        self.generate_expression(&e.this)?;
32417        self.write(")");
32418        Ok(())
32419    }
32420
32421    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
32422        // Output leading comments
32423        for comment in &e.leading_comments {
32424            self.write_formatted_comment(comment);
32425            if self.config.pretty {
32426                self.write_newline();
32427                self.write_indent();
32428            } else {
32429                self.write_space();
32430            }
32431        }
32432        // Python: INSERT [OVERWRITE] kind expressions source
32433        self.write_keyword("INSERT");
32434        if e.overwrite {
32435            self.write_space();
32436            self.write_keyword("OVERWRITE");
32437        }
32438        self.write_space();
32439        self.write(&e.kind);
32440        if self.config.pretty {
32441            self.indent_level += 1;
32442            for expr in &e.expressions {
32443                self.write_newline();
32444                self.write_indent();
32445                self.generate_expression(expr)?;
32446            }
32447            self.indent_level -= 1;
32448        } else {
32449            for expr in &e.expressions {
32450                self.write_space();
32451                self.generate_expression(expr)?;
32452            }
32453        }
32454        if let Some(source) = &e.source {
32455            if self.config.pretty {
32456                self.write_newline();
32457                self.write_indent();
32458            } else {
32459                self.write_space();
32460            }
32461            self.generate_expression(source)?;
32462        }
32463        Ok(())
32464    }
32465
32466    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
32467        // Python: NEXT VALUE FOR this [OVER (order)]
32468        self.write_keyword("NEXT VALUE FOR");
32469        self.write_space();
32470        self.generate_expression(&e.this)?;
32471        if let Some(order) = &e.order {
32472            self.write_space();
32473            self.write_keyword("OVER");
32474            self.write(" (");
32475            self.generate_expression(order)?;
32476            self.write(")");
32477        }
32478        Ok(())
32479    }
32480
32481    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
32482        // NORMAL(mean, stddev, gen)
32483        self.write_keyword("NORMAL");
32484        self.write("(");
32485        self.generate_expression(&e.this)?;
32486        if let Some(stddev) = &e.stddev {
32487            self.write(", ");
32488            self.generate_expression(stddev)?;
32489        }
32490        if let Some(gen) = &e.gen {
32491            self.write(", ");
32492            self.generate_expression(gen)?;
32493        }
32494        self.write(")");
32495        Ok(())
32496    }
32497
32498    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
32499        // NORMALIZE(this, form) or CASEFOLD version
32500        if e.is_casefold.is_some() {
32501            self.write_keyword("NORMALIZE_AND_CASEFOLD");
32502        } else {
32503            self.write_keyword("NORMALIZE");
32504        }
32505        self.write("(");
32506        self.generate_expression(&e.this)?;
32507        if let Some(form) = &e.form {
32508            self.write(", ");
32509            self.generate_expression(form)?;
32510        }
32511        self.write(")");
32512        Ok(())
32513    }
32514
32515    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
32516        // Python: [NOT ]NULL
32517        if e.allow_null.is_none() {
32518            self.write_keyword("NOT ");
32519        }
32520        self.write_keyword("NULL");
32521        Ok(())
32522    }
32523
32524    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
32525        // NULLIF(this, expression)
32526        self.write_keyword("NULLIF");
32527        self.write("(");
32528        self.generate_expression(&e.this)?;
32529        self.write(", ");
32530        self.generate_expression(&e.expression)?;
32531        self.write(")");
32532        Ok(())
32533    }
32534
32535    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
32536        // FORMAT(this, format, culture)
32537        self.write_keyword("FORMAT");
32538        self.write("(");
32539        self.generate_expression(&e.this)?;
32540        self.write(", '");
32541        self.write(&e.format);
32542        self.write("'");
32543        if let Some(culture) = &e.culture {
32544            self.write(", ");
32545            self.generate_expression(culture)?;
32546        }
32547        self.write(")");
32548        Ok(())
32549    }
32550
32551    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
32552        // OBJECT_AGG(key, value)
32553        self.write_keyword("OBJECT_AGG");
32554        self.write("(");
32555        self.generate_expression(&e.this)?;
32556        self.write(", ");
32557        self.generate_expression(&e.expression)?;
32558        self.write(")");
32559        Ok(())
32560    }
32561
32562    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
32563        // Python: Just returns the name
32564        self.generate_expression(&e.this)?;
32565        Ok(())
32566    }
32567
32568    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
32569        // OBJECT_INSERT(obj, key, value, [update_flag])
32570        self.write_keyword("OBJECT_INSERT");
32571        self.write("(");
32572        self.generate_expression(&e.this)?;
32573        if let Some(key) = &e.key {
32574            self.write(", ");
32575            self.generate_expression(key)?;
32576        }
32577        if let Some(value) = &e.value {
32578            self.write(", ");
32579            self.generate_expression(value)?;
32580        }
32581        if let Some(update_flag) = &e.update_flag {
32582            self.write(", ");
32583            self.generate_expression(update_flag)?;
32584        }
32585        self.write(")");
32586        Ok(())
32587    }
32588
32589    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
32590        // OFFSET value [ROW|ROWS]
32591        self.write_keyword("OFFSET");
32592        self.write_space();
32593        self.generate_expression(&e.this)?;
32594        // Output ROWS keyword only for TSQL/Oracle targets
32595        if e.rows == Some(true)
32596            && matches!(
32597                self.config.dialect,
32598                Some(crate::dialects::DialectType::TSQL)
32599                    | Some(crate::dialects::DialectType::Oracle)
32600            )
32601        {
32602            self.write_space();
32603            self.write_keyword("ROWS");
32604        }
32605        Ok(())
32606    }
32607
32608    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
32609        // QUALIFY condition (Snowflake/BigQuery)
32610        self.write_keyword("QUALIFY");
32611        self.write_space();
32612        self.generate_expression(&e.this)?;
32613        Ok(())
32614    }
32615
32616    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
32617        // ON CLUSTER cluster_name
32618        self.write_keyword("ON CLUSTER");
32619        self.write_space();
32620        self.generate_expression(&e.this)?;
32621        Ok(())
32622    }
32623
32624    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
32625        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
32626        self.write_keyword("ON COMMIT");
32627        if e.delete.is_some() {
32628            self.write_keyword(" DELETE ROWS");
32629        } else {
32630            self.write_keyword(" PRESERVE ROWS");
32631        }
32632        Ok(())
32633    }
32634
32635    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
32636        // Python: error/empty/null handling
32637        if let Some(empty) = &e.empty {
32638            self.generate_expression(empty)?;
32639            self.write_keyword(" ON EMPTY");
32640        }
32641        if let Some(error) = &e.error {
32642            if e.empty.is_some() {
32643                self.write_space();
32644            }
32645            self.generate_expression(error)?;
32646            self.write_keyword(" ON ERROR");
32647        }
32648        if let Some(null) = &e.null {
32649            if e.empty.is_some() || e.error.is_some() {
32650                self.write_space();
32651            }
32652            self.generate_expression(null)?;
32653            self.write_keyword(" ON NULL");
32654        }
32655        Ok(())
32656    }
32657
32658    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
32659        // Materialize doesn't support ON CONFLICT - skip entirely
32660        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
32661            return Ok(());
32662        }
32663        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
32664        if e.duplicate.is_some() {
32665            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
32666            self.write_keyword("ON DUPLICATE KEY UPDATE");
32667            for (i, expr) in e.expressions.iter().enumerate() {
32668                if i > 0 {
32669                    self.write(",");
32670                }
32671                self.write_space();
32672                self.generate_expression(expr)?;
32673            }
32674            return Ok(());
32675        } else {
32676            self.write_keyword("ON CONFLICT");
32677        }
32678        if let Some(constraint) = &e.constraint {
32679            self.write_keyword(" ON CONSTRAINT ");
32680            self.generate_expression(constraint)?;
32681        }
32682        if let Some(conflict_keys) = &e.conflict_keys {
32683            // conflict_keys can be a Tuple containing expressions
32684            if let Expression::Tuple(t) = conflict_keys.as_ref() {
32685                self.write("(");
32686                for (i, expr) in t.expressions.iter().enumerate() {
32687                    if i > 0 {
32688                        self.write(", ");
32689                    }
32690                    self.generate_expression(expr)?;
32691                }
32692                self.write(")");
32693            } else {
32694                self.write("(");
32695                self.generate_expression(conflict_keys)?;
32696                self.write(")");
32697            }
32698        }
32699        if let Some(index_predicate) = &e.index_predicate {
32700            self.write_keyword(" WHERE ");
32701            self.generate_expression(index_predicate)?;
32702        }
32703        if let Some(action) = &e.action {
32704            // Check if action is "NOTHING" or an UPDATE set
32705            if let Expression::Identifier(id) = action.as_ref() {
32706                if id.name.eq_ignore_ascii_case("NOTHING") {
32707                    self.write_keyword(" DO NOTHING");
32708                } else {
32709                    self.write_keyword(" DO ");
32710                    self.generate_expression(action)?;
32711                }
32712            } else if let Expression::Tuple(t) = action.as_ref() {
32713                // DO UPDATE SET col1 = val1, col2 = val2
32714                self.write_keyword(" DO UPDATE SET ");
32715                for (i, expr) in t.expressions.iter().enumerate() {
32716                    if i > 0 {
32717                        self.write(", ");
32718                    }
32719                    self.generate_expression(expr)?;
32720                }
32721            } else {
32722                self.write_keyword(" DO ");
32723                self.generate_expression(action)?;
32724            }
32725        }
32726        // WHERE clause for the UPDATE action
32727        if let Some(where_) = &e.where_ {
32728            self.write_keyword(" WHERE ");
32729            self.generate_expression(where_)?;
32730        }
32731        Ok(())
32732    }
32733
32734    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
32735        // ON property_value
32736        self.write_keyword("ON");
32737        self.write_space();
32738        self.generate_expression(&e.this)?;
32739        Ok(())
32740    }
32741
32742    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
32743        // Python: this expression (e.g., column opclass)
32744        self.generate_expression(&e.this)?;
32745        self.write_space();
32746        self.generate_expression(&e.expression)?;
32747        Ok(())
32748    }
32749
32750    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
32751        // Python: OPENJSON(this[, path]) [WITH (columns)]
32752        self.write_keyword("OPENJSON");
32753        self.write("(");
32754        self.generate_expression(&e.this)?;
32755        if let Some(path) = &e.path {
32756            self.write(", ");
32757            self.generate_expression(path)?;
32758        }
32759        self.write(")");
32760        if !e.expressions.is_empty() {
32761            self.write_keyword(" WITH");
32762            if self.config.pretty {
32763                self.write(" (\n");
32764                self.indent_level += 2;
32765                for (i, expr) in e.expressions.iter().enumerate() {
32766                    if i > 0 {
32767                        self.write(",\n");
32768                    }
32769                    self.write_indent();
32770                    self.generate_expression(expr)?;
32771                }
32772                self.write("\n");
32773                self.indent_level -= 2;
32774                self.write(")");
32775            } else {
32776                self.write(" (");
32777                for (i, expr) in e.expressions.iter().enumerate() {
32778                    if i > 0 {
32779                        self.write(", ");
32780                    }
32781                    self.generate_expression(expr)?;
32782                }
32783                self.write(")");
32784            }
32785        }
32786        Ok(())
32787    }
32788
32789    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
32790        // Python: this kind [path] [AS JSON]
32791        self.generate_expression(&e.this)?;
32792        self.write_space();
32793        // Use parsed data_type if available, otherwise fall back to kind string
32794        if let Some(ref dt) = e.data_type {
32795            self.generate_data_type(dt)?;
32796        } else if !e.kind.is_empty() {
32797            self.write(&e.kind);
32798        }
32799        if let Some(path) = &e.path {
32800            self.write_space();
32801            self.generate_expression(path)?;
32802        }
32803        if e.as_json.is_some() {
32804            self.write_keyword(" AS JSON");
32805        }
32806        Ok(())
32807    }
32808
32809    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
32810        // this OPERATOR(op) expression
32811        self.generate_expression(&e.this)?;
32812        self.write_space();
32813        if let Some(op) = &e.operator {
32814            self.write_keyword("OPERATOR");
32815            self.write("(");
32816            self.generate_expression(op)?;
32817            self.write(")");
32818        }
32819        // Emit inline comments between OPERATOR() and the RHS
32820        for comment in &e.comments {
32821            self.write_space();
32822            self.write_formatted_comment(comment);
32823        }
32824        self.write_space();
32825        self.generate_expression(&e.expression)?;
32826        Ok(())
32827    }
32828
32829    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
32830        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
32831        self.write_keyword("ORDER BY");
32832        let pretty_clickhouse_single_paren = self.config.pretty
32833            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
32834            && e.expressions.len() == 1
32835            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
32836        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
32837            && e.expressions.len() == 1
32838            && matches!(e.expressions[0].this, Expression::Tuple(_))
32839            && !e.expressions[0].desc
32840            && e.expressions[0].nulls_first.is_none();
32841
32842        if pretty_clickhouse_single_paren {
32843            self.write_space();
32844            if let Expression::Paren(p) = &e.expressions[0].this {
32845                self.write("(");
32846                self.write_newline();
32847                self.indent_level += 1;
32848                self.write_indent();
32849                self.generate_expression(&p.this)?;
32850                self.indent_level -= 1;
32851                self.write_newline();
32852                self.write(")");
32853            }
32854            return Ok(());
32855        }
32856
32857        if clickhouse_single_tuple {
32858            self.write_space();
32859            if let Expression::Tuple(t) = &e.expressions[0].this {
32860                self.write("(");
32861                for (i, expr) in t.expressions.iter().enumerate() {
32862                    if i > 0 {
32863                        self.write(", ");
32864                    }
32865                    self.generate_expression(expr)?;
32866                }
32867                self.write(")");
32868            }
32869            return Ok(());
32870        }
32871
32872        self.write_space();
32873        for (i, ordered) in e.expressions.iter().enumerate() {
32874            if i > 0 {
32875                self.write(", ");
32876            }
32877            self.generate_expression(&ordered.this)?;
32878            if ordered.desc {
32879                self.write_space();
32880                self.write_keyword("DESC");
32881            } else if ordered.explicit_asc {
32882                self.write_space();
32883                self.write_keyword("ASC");
32884            }
32885            if let Some(nulls_first) = ordered.nulls_first {
32886                // In Dremio, NULLS LAST is the default, so skip generating it
32887                let skip_nulls_last =
32888                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
32889                if !skip_nulls_last {
32890                    self.write_space();
32891                    self.write_keyword("NULLS");
32892                    self.write_space();
32893                    if nulls_first {
32894                        self.write_keyword("FIRST");
32895                    } else {
32896                        self.write_keyword("LAST");
32897                    }
32898                }
32899            }
32900        }
32901        Ok(())
32902    }
32903
32904    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
32905        // OUTPUT(model)
32906        self.write_keyword("OUTPUT");
32907        self.write("(");
32908        if self.config.pretty {
32909            self.indent_level += 1;
32910            self.write_newline();
32911            self.write_indent();
32912            self.generate_expression(&e.this)?;
32913            self.indent_level -= 1;
32914            self.write_newline();
32915        } else {
32916            self.generate_expression(&e.this)?;
32917        }
32918        self.write(")");
32919        Ok(())
32920    }
32921
32922    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
32923        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
32924        self.write_keyword("TRUNCATE");
32925        if let Some(this) = &e.this {
32926            self.write_space();
32927            self.generate_expression(this)?;
32928        }
32929        if e.with_count.is_some() {
32930            self.write_keyword(" WITH COUNT");
32931        } else {
32932            self.write_keyword(" WITHOUT COUNT");
32933        }
32934        Ok(())
32935    }
32936
32937    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
32938        // Python: name(expressions)(params)
32939        self.generate_expression(&e.this)?;
32940        self.write("(");
32941        for (i, expr) in e.expressions.iter().enumerate() {
32942            if i > 0 {
32943                self.write(", ");
32944            }
32945            self.generate_expression(expr)?;
32946        }
32947        self.write(")(");
32948        for (i, param) in e.params.iter().enumerate() {
32949            if i > 0 {
32950                self.write(", ");
32951            }
32952            self.generate_expression(param)?;
32953        }
32954        self.write(")");
32955        Ok(())
32956    }
32957
32958    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
32959        // PARSE_DATETIME(format, this) or similar
32960        self.write_keyword("PARSE_DATETIME");
32961        self.write("(");
32962        if let Some(format) = &e.format {
32963            self.write("'");
32964            self.write(format);
32965            self.write("', ");
32966        }
32967        self.generate_expression(&e.this)?;
32968        if let Some(zone) = &e.zone {
32969            self.write(", ");
32970            self.generate_expression(zone)?;
32971        }
32972        self.write(")");
32973        Ok(())
32974    }
32975
32976    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
32977        // PARSE_IP(this, type, permissive)
32978        self.write_keyword("PARSE_IP");
32979        self.write("(");
32980        self.generate_expression(&e.this)?;
32981        if let Some(type_) = &e.type_ {
32982            self.write(", ");
32983            self.generate_expression(type_)?;
32984        }
32985        if let Some(permissive) = &e.permissive {
32986            self.write(", ");
32987            self.generate_expression(permissive)?;
32988        }
32989        self.write(")");
32990        Ok(())
32991    }
32992
32993    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
32994        // PARSE_JSON(this, [expression])
32995        self.write_keyword("PARSE_JSON");
32996        self.write("(");
32997        self.generate_expression(&e.this)?;
32998        if let Some(expression) = &e.expression {
32999            self.write(", ");
33000            self.generate_expression(expression)?;
33001        }
33002        self.write(")");
33003        Ok(())
33004    }
33005
33006    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
33007        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
33008        self.write_keyword("PARSE_TIME");
33009        self.write("(");
33010        self.write(&format!("'{}'", e.format));
33011        self.write(", ");
33012        self.generate_expression(&e.this)?;
33013        self.write(")");
33014        Ok(())
33015    }
33016
33017    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
33018        // PARSE_URL(this, [part_to_extract], [key], [permissive])
33019        self.write_keyword("PARSE_URL");
33020        self.write("(");
33021        self.generate_expression(&e.this)?;
33022        if let Some(part) = &e.part_to_extract {
33023            self.write(", ");
33024            self.generate_expression(part)?;
33025        }
33026        if let Some(key) = &e.key {
33027            self.write(", ");
33028            self.generate_expression(key)?;
33029        }
33030        if let Some(permissive) = &e.permissive {
33031            self.write(", ");
33032            self.generate_expression(permissive)?;
33033        }
33034        self.write(")");
33035        Ok(())
33036    }
33037
33038    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
33039        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
33040        if e.subpartition {
33041            self.write_keyword("SUBPARTITION");
33042        } else {
33043            self.write_keyword("PARTITION");
33044        }
33045        self.write("(");
33046        for (i, expr) in e.expressions.iter().enumerate() {
33047            if i > 0 {
33048                self.write(", ");
33049            }
33050            self.generate_expression(expr)?;
33051        }
33052        self.write(")");
33053        Ok(())
33054    }
33055
33056    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
33057        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
33058        if let Some(this) = &e.this {
33059            if let Some(expression) = &e.expression {
33060                // WITH (MODULUS this, REMAINDER expression)
33061                self.write_keyword("WITH");
33062                self.write(" (");
33063                self.write_keyword("MODULUS");
33064                self.write_space();
33065                self.generate_expression(this)?;
33066                self.write(", ");
33067                self.write_keyword("REMAINDER");
33068                self.write_space();
33069                self.generate_expression(expression)?;
33070                self.write(")");
33071            } else {
33072                // IN (this) - this could be a list
33073                self.write_keyword("IN");
33074                self.write(" (");
33075                self.generate_partition_bound_values(this)?;
33076                self.write(")");
33077            }
33078        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
33079            // FROM (from_expressions) TO (to_expressions)
33080            self.write_keyword("FROM");
33081            self.write(" (");
33082            self.generate_partition_bound_values(from)?;
33083            self.write(") ");
33084            self.write_keyword("TO");
33085            self.write(" (");
33086            self.generate_partition_bound_values(to)?;
33087            self.write(")");
33088        }
33089        Ok(())
33090    }
33091
33092    /// Generate partition bound values - handles Tuple expressions by outputting
33093    /// contents without wrapping parens (since caller provides the parens)
33094    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
33095        if let Expression::Tuple(t) = expr {
33096            for (i, e) in t.expressions.iter().enumerate() {
33097                if i > 0 {
33098                    self.write(", ");
33099                }
33100                self.generate_expression(e)?;
33101            }
33102            Ok(())
33103        } else {
33104            self.generate_expression(expr)
33105        }
33106    }
33107
33108    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
33109        // PARTITION BY LIST (partition_expressions) (create_expressions)
33110        self.write_keyword("PARTITION BY LIST");
33111        if let Some(partition_exprs) = &e.partition_expressions {
33112            self.write(" (");
33113            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
33114            self.generate_doris_partition_expressions(partition_exprs)?;
33115            self.write(")");
33116        }
33117        if let Some(create_exprs) = &e.create_expressions {
33118            self.write(" (");
33119            // Unwrap Tuple for partition definitions
33120            self.generate_doris_partition_definitions(create_exprs)?;
33121            self.write(")");
33122        }
33123        Ok(())
33124    }
33125
33126    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
33127        // PARTITION BY RANGE (partition_expressions) (create_expressions)
33128        self.write_keyword("PARTITION BY RANGE");
33129        if let Some(partition_exprs) = &e.partition_expressions {
33130            self.write(" (");
33131            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
33132            self.generate_doris_partition_expressions(partition_exprs)?;
33133            self.write(")");
33134        }
33135        if let Some(create_exprs) = &e.create_expressions {
33136            self.write(" (");
33137            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
33138            self.generate_doris_partition_definitions(create_exprs)?;
33139            self.write(")");
33140        }
33141        Ok(())
33142    }
33143
33144    /// Generate Doris partition column expressions (unwrap Tuple)
33145    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
33146        if let Expression::Tuple(t) = expr {
33147            for (i, e) in t.expressions.iter().enumerate() {
33148                if i > 0 {
33149                    self.write(", ");
33150                }
33151                self.generate_expression(e)?;
33152            }
33153        } else {
33154            self.generate_expression(expr)?;
33155        }
33156        Ok(())
33157    }
33158
33159    /// Generate Doris partition definitions (comma-separated Partition expressions)
33160    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
33161        match expr {
33162            Expression::Tuple(t) => {
33163                // Multiple partitions, comma-separated
33164                for (i, part) in t.expressions.iter().enumerate() {
33165                    if i > 0 {
33166                        self.write(", ");
33167                    }
33168                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
33169                    if let Expression::Partition(p) = part {
33170                        for (j, inner) in p.expressions.iter().enumerate() {
33171                            if j > 0 {
33172                                self.write(", ");
33173                            }
33174                            self.generate_expression(inner)?;
33175                        }
33176                    } else {
33177                        self.generate_expression(part)?;
33178                    }
33179                }
33180            }
33181            Expression::PartitionByRangePropertyDynamic(_) => {
33182                // Dynamic partition - FROM/TO/INTERVAL
33183                self.generate_expression(expr)?;
33184            }
33185            _ => {
33186                self.generate_expression(expr)?;
33187            }
33188        }
33189        Ok(())
33190    }
33191
33192    fn generate_partition_by_range_property_dynamic(
33193        &mut self,
33194        e: &PartitionByRangePropertyDynamic,
33195    ) -> Result<()> {
33196        if e.use_start_end {
33197            // StarRocks: START ('val') END ('val') EVERY (expr)
33198            if let Some(start) = &e.start {
33199                self.write_keyword("START");
33200                self.write(" (");
33201                self.generate_expression(start)?;
33202                self.write(")");
33203            }
33204            if let Some(end) = &e.end {
33205                self.write_space();
33206                self.write_keyword("END");
33207                self.write(" (");
33208                self.generate_expression(end)?;
33209                self.write(")");
33210            }
33211            if let Some(every) = &e.every {
33212                self.write_space();
33213                self.write_keyword("EVERY");
33214                self.write(" (");
33215                // Use unquoted interval format for StarRocks
33216                self.generate_doris_interval(every)?;
33217                self.write(")");
33218            }
33219        } else {
33220            // Doris: FROM (start) TO (end) INTERVAL n UNIT
33221            if let Some(start) = &e.start {
33222                self.write_keyword("FROM");
33223                self.write(" (");
33224                self.generate_expression(start)?;
33225                self.write(")");
33226            }
33227            if let Some(end) = &e.end {
33228                self.write_space();
33229                self.write_keyword("TO");
33230                self.write(" (");
33231                self.generate_expression(end)?;
33232                self.write(")");
33233            }
33234            if let Some(every) = &e.every {
33235                self.write_space();
33236                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
33237                self.generate_doris_interval(every)?;
33238            }
33239        }
33240        Ok(())
33241    }
33242
33243    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
33244    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
33245        if let Expression::Interval(interval) = expr {
33246            self.write_keyword("INTERVAL");
33247            if let Some(ref value) = interval.this {
33248                self.write_space();
33249                // If the value is a string literal that looks like a number,
33250                // output it without quotes (matching Python sqlglot's
33251                // partitionbyrangepropertydynamic_sql which converts back to number)
33252                match value {
33253                    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()) => {
33254                        if let Literal::String(s) = lit.as_ref() {
33255                            self.write(s);
33256                        }
33257                    }
33258                    _ => {
33259                        self.generate_expression(value)?;
33260                    }
33261                }
33262            }
33263            if let Some(ref unit_spec) = interval.unit {
33264                self.write_space();
33265                self.write_interval_unit_spec(unit_spec)?;
33266            }
33267            Ok(())
33268        } else {
33269            self.generate_expression(expr)
33270        }
33271    }
33272
33273    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
33274        // TRUNCATE(expression, this)
33275        self.write_keyword("TRUNCATE");
33276        self.write("(");
33277        self.generate_expression(&e.expression)?;
33278        self.write(", ");
33279        self.generate_expression(&e.this)?;
33280        self.write(")");
33281        Ok(())
33282    }
33283
33284    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
33285        // Doris: PARTITION name VALUES IN (val1, val2)
33286        self.write_keyword("PARTITION");
33287        self.write_space();
33288        self.generate_expression(&e.this)?;
33289        self.write_space();
33290        self.write_keyword("VALUES IN");
33291        self.write(" (");
33292        for (i, expr) in e.expressions.iter().enumerate() {
33293            if i > 0 {
33294                self.write(", ");
33295            }
33296            self.generate_expression(expr)?;
33297        }
33298        self.write(")");
33299        Ok(())
33300    }
33301
33302    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
33303        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
33304        // TSQL ranges have no expressions and just use `this TO expression`
33305        if e.expressions.is_empty() && e.expression.is_some() {
33306            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
33307            self.generate_expression(&e.this)?;
33308            self.write_space();
33309            self.write_keyword("TO");
33310            self.write_space();
33311            self.generate_expression(e.expression.as_ref().unwrap())?;
33312            return Ok(());
33313        }
33314
33315        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
33316        self.write_keyword("PARTITION");
33317        self.write_space();
33318        self.generate_expression(&e.this)?;
33319        self.write_space();
33320
33321        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
33322        if e.expressions.len() == 1 {
33323            // Single value: VALUES LESS THAN (val)
33324            self.write_keyword("VALUES LESS THAN");
33325            self.write(" (");
33326            self.generate_expression(&e.expressions[0])?;
33327            self.write(")");
33328        } else if !e.expressions.is_empty() {
33329            // Multiple values with Tuple: VALUES [(val1), (val2))
33330            self.write_keyword("VALUES");
33331            self.write(" [");
33332            for (i, expr) in e.expressions.iter().enumerate() {
33333                if i > 0 {
33334                    self.write(", ");
33335                }
33336                // If the expr is a Tuple, generate its contents wrapped in parens
33337                if let Expression::Tuple(t) = expr {
33338                    self.write("(");
33339                    for (j, inner) in t.expressions.iter().enumerate() {
33340                        if j > 0 {
33341                            self.write(", ");
33342                        }
33343                        self.generate_expression(inner)?;
33344                    }
33345                    self.write(")");
33346                } else {
33347                    self.write("(");
33348                    self.generate_expression(expr)?;
33349                    self.write(")");
33350                }
33351            }
33352            self.write(")");
33353        }
33354        Ok(())
33355    }
33356
33357    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
33358        // BUCKET(this, expression)
33359        self.write_keyword("BUCKET");
33360        self.write("(");
33361        self.generate_expression(&e.this)?;
33362        self.write(", ");
33363        self.generate_expression(&e.expression)?;
33364        self.write(")");
33365        Ok(())
33366    }
33367
33368    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
33369        // BigQuery table property: PARTITION BY expression [, expression ...]
33370        self.write_keyword("PARTITION BY");
33371        self.write_space();
33372        for (i, expr) in e.expressions.iter().enumerate() {
33373            if i > 0 {
33374                self.write(", ");
33375            }
33376            self.generate_expression(expr)?;
33377        }
33378        Ok(())
33379    }
33380
33381    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
33382        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
33383        if matches!(
33384            self.config.dialect,
33385            Some(crate::dialects::DialectType::Teradata)
33386                | Some(crate::dialects::DialectType::ClickHouse)
33387        ) {
33388            self.write_keyword("PARTITION BY");
33389        } else {
33390            self.write_keyword("PARTITIONED BY");
33391        }
33392        self.write_space();
33393        // In pretty mode, always use multiline tuple format for PARTITIONED BY
33394        if self.config.pretty {
33395            if let Expression::Tuple(ref tuple) = *e.this {
33396                self.write("(");
33397                self.write_newline();
33398                self.indent_level += 1;
33399                for (i, expr) in tuple.expressions.iter().enumerate() {
33400                    if i > 0 {
33401                        self.write(",");
33402                        self.write_newline();
33403                    }
33404                    self.write_indent();
33405                    self.generate_expression(expr)?;
33406                }
33407                self.indent_level -= 1;
33408                self.write_newline();
33409                self.write(")");
33410            } else {
33411                self.generate_expression(&e.this)?;
33412            }
33413        } else {
33414            self.generate_expression(&e.this)?;
33415        }
33416        Ok(())
33417    }
33418
33419    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
33420        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
33421        self.write_keyword("PARTITION OF");
33422        self.write_space();
33423        self.generate_expression(&e.this)?;
33424        // Check if expression is a PartitionBoundSpec
33425        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
33426            self.write_space();
33427            self.write_keyword("FOR VALUES");
33428            self.write_space();
33429            self.generate_expression(&e.expression)?;
33430        } else {
33431            self.write_space();
33432            self.write_keyword("DEFAULT");
33433        }
33434        Ok(())
33435    }
33436
33437    fn generate_period_for_system_time_constraint(
33438        &mut self,
33439        e: &PeriodForSystemTimeConstraint,
33440    ) -> Result<()> {
33441        // PERIOD FOR SYSTEM_TIME (this, expression)
33442        self.write_keyword("PERIOD FOR SYSTEM_TIME");
33443        self.write(" (");
33444        self.generate_expression(&e.this)?;
33445        self.write(", ");
33446        self.generate_expression(&e.expression)?;
33447        self.write(")");
33448        Ok(())
33449    }
33450
33451    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
33452        // value AS alias
33453        // The alias can be an identifier or an expression (e.g., string concatenation)
33454        self.generate_expression(&e.this)?;
33455        self.write_space();
33456        self.write_keyword("AS");
33457        self.write_space();
33458        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
33459        if self.config.unpivot_aliases_are_identifiers {
33460            match &e.alias {
33461                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
33462                    let Literal::String(s) = lit.as_ref() else {
33463                        unreachable!()
33464                    };
33465                    // Convert string literal to identifier
33466                    self.generate_identifier(&Identifier::new(s.clone()))?;
33467                }
33468                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
33469                    let Literal::Number(n) = lit.as_ref() else {
33470                        unreachable!()
33471                    };
33472                    // Convert number literal to quoted identifier
33473                    let mut id = Identifier::new(n.clone());
33474                    id.quoted = true;
33475                    self.generate_identifier(&id)?;
33476                }
33477                other => {
33478                    self.generate_expression(other)?;
33479                }
33480            }
33481        } else {
33482            self.generate_expression(&e.alias)?;
33483        }
33484        Ok(())
33485    }
33486
33487    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
33488        // ANY or ANY [expression]
33489        self.write_keyword("ANY");
33490        if let Some(this) = &e.this {
33491            self.write_space();
33492            self.generate_expression(this)?;
33493        }
33494        Ok(())
33495    }
33496
33497    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
33498        // ML.PREDICT(MODEL this, expression, [params_struct])
33499        self.write_keyword("ML.PREDICT");
33500        self.write("(");
33501        self.write_keyword("MODEL");
33502        self.write_space();
33503        self.generate_expression(&e.this)?;
33504        self.write(", ");
33505        self.generate_expression(&e.expression)?;
33506        if let Some(params) = &e.params_struct {
33507            self.write(", ");
33508            self.generate_expression(params)?;
33509        }
33510        self.write(")");
33511        Ok(())
33512    }
33513
33514    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
33515        // PREVIOUS_DAY(this, expression)
33516        self.write_keyword("PREVIOUS_DAY");
33517        self.write("(");
33518        self.generate_expression(&e.this)?;
33519        self.write(", ");
33520        self.generate_expression(&e.expression)?;
33521        self.write(")");
33522        Ok(())
33523    }
33524
33525    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
33526        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
33527        self.write_keyword("PRIMARY KEY");
33528        if let Some(name) = &e.this {
33529            self.write_space();
33530            self.generate_expression(name)?;
33531        }
33532        if !e.expressions.is_empty() {
33533            self.write(" (");
33534            for (i, expr) in e.expressions.iter().enumerate() {
33535                if i > 0 {
33536                    self.write(", ");
33537                }
33538                self.generate_expression(expr)?;
33539            }
33540            self.write(")");
33541        }
33542        if let Some(include) = &e.include {
33543            self.write_space();
33544            self.generate_expression(include)?;
33545        }
33546        if !e.options.is_empty() {
33547            self.write_space();
33548            for (i, opt) in e.options.iter().enumerate() {
33549                if i > 0 {
33550                    self.write_space();
33551                }
33552                self.generate_expression(opt)?;
33553            }
33554        }
33555        Ok(())
33556    }
33557
33558    fn generate_primary_key_column_constraint(
33559        &mut self,
33560        _e: &PrimaryKeyColumnConstraint,
33561    ) -> Result<()> {
33562        // PRIMARY KEY constraint at column level
33563        self.write_keyword("PRIMARY KEY");
33564        Ok(())
33565    }
33566
33567    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
33568        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
33569        self.write_keyword("PATH");
33570        self.write_space();
33571        self.generate_expression(&e.this)?;
33572        Ok(())
33573    }
33574
33575    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
33576        // PROJECTION this (expression)
33577        self.write_keyword("PROJECTION");
33578        self.write_space();
33579        self.generate_expression(&e.this)?;
33580        self.write(" (");
33581        self.generate_expression(&e.expression)?;
33582        self.write(")");
33583        Ok(())
33584    }
33585
33586    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
33587        // Properties list
33588        for (i, prop) in e.expressions.iter().enumerate() {
33589            if i > 0 {
33590                self.write(", ");
33591            }
33592            self.generate_expression(prop)?;
33593        }
33594        Ok(())
33595    }
33596
33597    fn generate_property(&mut self, e: &Property) -> Result<()> {
33598        // name=value
33599        self.generate_expression(&e.this)?;
33600        if let Some(value) = &e.value {
33601            self.write("=");
33602            self.generate_expression(value)?;
33603        }
33604        Ok(())
33605    }
33606
33607    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
33608        self.write_keyword("OPTIONS");
33609        if e.entries.is_empty() {
33610            self.write(" ()");
33611            return Ok(());
33612        }
33613
33614        if self.config.pretty {
33615            self.write(" (");
33616            self.write_newline();
33617            self.indent_level += 1;
33618            for (i, entry) in e.entries.iter().enumerate() {
33619                if i > 0 {
33620                    self.write(",");
33621                    self.write_newline();
33622                }
33623                self.write_indent();
33624                self.generate_identifier(&entry.key)?;
33625                self.write("=");
33626                self.generate_expression(&entry.value)?;
33627            }
33628            self.indent_level -= 1;
33629            self.write_newline();
33630            self.write(")");
33631        } else {
33632            self.write(" (");
33633            for (i, entry) in e.entries.iter().enumerate() {
33634                if i > 0 {
33635                    self.write(", ");
33636                }
33637                self.generate_identifier(&entry.key)?;
33638                self.write("=");
33639                self.generate_expression(&entry.value)?;
33640            }
33641            self.write(")");
33642        }
33643        Ok(())
33644    }
33645
33646    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
33647    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
33648        self.write_keyword("OPTIONS");
33649        self.write(" (");
33650        for (i, opt) in options.iter().enumerate() {
33651            if i > 0 {
33652                self.write(", ");
33653            }
33654            self.generate_option_expression(opt)?;
33655        }
33656        self.write(")");
33657        Ok(())
33658    }
33659
33660    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
33661    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
33662        self.write_keyword("PROPERTIES");
33663        self.write(" (");
33664        for (i, prop) in properties.iter().enumerate() {
33665            if i > 0 {
33666                self.write(", ");
33667            }
33668            self.generate_option_expression(prop)?;
33669        }
33670        self.write(")");
33671        Ok(())
33672    }
33673
33674    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
33675    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
33676        self.write_keyword("ENVIRONMENT");
33677        self.write(" (");
33678        for (i, env_item) in environment.iter().enumerate() {
33679            if i > 0 {
33680                self.write(", ");
33681            }
33682            self.generate_environment_expression(env_item)?;
33683        }
33684        self.write(")");
33685        Ok(())
33686    }
33687
33688    /// Generate an environment expression with spaces around =
33689    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
33690        match expr {
33691            Expression::Eq(eq) => {
33692                // Generate key = value with spaces (Databricks ENVIRONMENT style)
33693                self.generate_expression(&eq.left)?;
33694                self.write(" = ");
33695                self.generate_expression(&eq.right)?;
33696                Ok(())
33697            }
33698            _ => self.generate_expression(expr),
33699        }
33700    }
33701
33702    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
33703    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
33704        self.write_keyword("TBLPROPERTIES");
33705        if self.config.pretty {
33706            self.write(" (");
33707            self.write_newline();
33708            self.indent_level += 1;
33709            for (i, opt) in options.iter().enumerate() {
33710                if i > 0 {
33711                    self.write(",");
33712                    self.write_newline();
33713                }
33714                self.write_indent();
33715                self.generate_option_expression(opt)?;
33716            }
33717            self.indent_level -= 1;
33718            self.write_newline();
33719            self.write(")");
33720        } else {
33721            self.write(" (");
33722            for (i, opt) in options.iter().enumerate() {
33723                if i > 0 {
33724                    self.write(", ");
33725                }
33726                self.generate_option_expression(opt)?;
33727            }
33728            self.write(")");
33729        }
33730        Ok(())
33731    }
33732
33733    /// Generate an option expression without spaces around =
33734    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
33735        match expr {
33736            Expression::Eq(eq) => {
33737                // Generate key=value without spaces
33738                self.generate_expression(&eq.left)?;
33739                self.write("=");
33740                self.generate_expression(&eq.right)?;
33741                Ok(())
33742            }
33743            _ => self.generate_expression(expr),
33744        }
33745    }
33746
33747    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
33748        // Just output the name
33749        self.generate_expression(&e.this)?;
33750        Ok(())
33751    }
33752
33753    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
33754        // PUT source_file @stage [options]
33755        self.write_keyword("PUT");
33756        self.write_space();
33757
33758        // Source file path - preserve original quoting
33759        if e.source_quoted {
33760            self.write("'");
33761            self.write(&e.source);
33762            self.write("'");
33763        } else {
33764            self.write(&e.source);
33765        }
33766
33767        self.write_space();
33768
33769        // Target stage reference - output the string directly (includes @)
33770        if let Expression::Literal(lit) = &e.target {
33771            if let Literal::String(s) = lit.as_ref() {
33772                self.write(s);
33773            }
33774        } else {
33775            self.generate_expression(&e.target)?;
33776        }
33777
33778        // Optional parameters: KEY=VALUE
33779        for param in &e.params {
33780            self.write_space();
33781            self.write(&param.name);
33782            if let Some(ref value) = param.value {
33783                self.write("=");
33784                self.generate_expression(value)?;
33785            }
33786        }
33787
33788        Ok(())
33789    }
33790
33791    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
33792        // QUANTILE(this, quantile)
33793        self.write_keyword("QUANTILE");
33794        self.write("(");
33795        self.generate_expression(&e.this)?;
33796        if let Some(quantile) = &e.quantile {
33797            self.write(", ");
33798            self.generate_expression(quantile)?;
33799        }
33800        self.write(")");
33801        Ok(())
33802    }
33803
33804    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
33805        // QUERY_BAND = this [UPDATE] [FOR scope]
33806        if matches!(
33807            self.config.dialect,
33808            Some(crate::dialects::DialectType::Teradata)
33809        ) {
33810            self.write_keyword("SET");
33811            self.write_space();
33812        }
33813        self.write_keyword("QUERY_BAND");
33814        self.write(" = ");
33815        self.generate_expression(&e.this)?;
33816        if e.update.is_some() {
33817            self.write_space();
33818            self.write_keyword("UPDATE");
33819        }
33820        if let Some(scope) = &e.scope {
33821            self.write_space();
33822            self.write_keyword("FOR");
33823            self.write_space();
33824            self.generate_expression(scope)?;
33825        }
33826        Ok(())
33827    }
33828
33829    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
33830        // this = expression
33831        self.generate_expression(&e.this)?;
33832        if let Some(expression) = &e.expression {
33833            self.write(" = ");
33834            self.generate_expression(expression)?;
33835        }
33836        Ok(())
33837    }
33838
33839    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
33840        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
33841        self.write_keyword("TRANSFORM");
33842        self.write("(");
33843        for (i, expr) in e.expressions.iter().enumerate() {
33844            if i > 0 {
33845                self.write(", ");
33846            }
33847            self.generate_expression(expr)?;
33848        }
33849        self.write(")");
33850        if let Some(row_format_before) = &e.row_format_before {
33851            self.write_space();
33852            self.generate_expression(row_format_before)?;
33853        }
33854        if let Some(record_writer) = &e.record_writer {
33855            self.write_space();
33856            self.write_keyword("RECORDWRITER");
33857            self.write_space();
33858            self.generate_expression(record_writer)?;
33859        }
33860        if let Some(command_script) = &e.command_script {
33861            self.write_space();
33862            self.write_keyword("USING");
33863            self.write_space();
33864            self.generate_expression(command_script)?;
33865        }
33866        if let Some(schema) = &e.schema {
33867            self.write_space();
33868            self.write_keyword("AS");
33869            self.write_space();
33870            self.generate_expression(schema)?;
33871        }
33872        if let Some(row_format_after) = &e.row_format_after {
33873            self.write_space();
33874            self.generate_expression(row_format_after)?;
33875        }
33876        if let Some(record_reader) = &e.record_reader {
33877            self.write_space();
33878            self.write_keyword("RECORDREADER");
33879            self.write_space();
33880            self.generate_expression(record_reader)?;
33881        }
33882        Ok(())
33883    }
33884
33885    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
33886        // RANDN([seed])
33887        self.write_keyword("RANDN");
33888        self.write("(");
33889        if let Some(this) = &e.this {
33890            self.generate_expression(this)?;
33891        }
33892        self.write(")");
33893        Ok(())
33894    }
33895
33896    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
33897        // RANDSTR(this, [generator])
33898        self.write_keyword("RANDSTR");
33899        self.write("(");
33900        self.generate_expression(&e.this)?;
33901        if let Some(generator) = &e.generator {
33902            self.write(", ");
33903            self.generate_expression(generator)?;
33904        }
33905        self.write(")");
33906        Ok(())
33907    }
33908
33909    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
33910        // RANGE_BUCKET(this, expression)
33911        self.write_keyword("RANGE_BUCKET");
33912        self.write("(");
33913        self.generate_expression(&e.this)?;
33914        self.write(", ");
33915        self.generate_expression(&e.expression)?;
33916        self.write(")");
33917        Ok(())
33918    }
33919
33920    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
33921        // RANGE_N(this BETWEEN expressions [EACH each])
33922        self.write_keyword("RANGE_N");
33923        self.write("(");
33924        self.generate_expression(&e.this)?;
33925        self.write_space();
33926        self.write_keyword("BETWEEN");
33927        self.write_space();
33928        for (i, expr) in e.expressions.iter().enumerate() {
33929            if i > 0 {
33930                self.write(", ");
33931            }
33932            self.generate_expression(expr)?;
33933        }
33934        if let Some(each) = &e.each {
33935            self.write_space();
33936            self.write_keyword("EACH");
33937            self.write_space();
33938            self.generate_expression(each)?;
33939        }
33940        self.write(")");
33941        Ok(())
33942    }
33943
33944    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
33945        // READ_CSV(this, expressions...)
33946        self.write_keyword("READ_CSV");
33947        self.write("(");
33948        self.generate_expression(&e.this)?;
33949        for expr in &e.expressions {
33950            self.write(", ");
33951            self.generate_expression(expr)?;
33952        }
33953        self.write(")");
33954        Ok(())
33955    }
33956
33957    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
33958        // READ_PARQUET(expressions...)
33959        self.write_keyword("READ_PARQUET");
33960        self.write("(");
33961        for (i, expr) in e.expressions.iter().enumerate() {
33962            if i > 0 {
33963                self.write(", ");
33964            }
33965            self.generate_expression(expr)?;
33966        }
33967        self.write(")");
33968        Ok(())
33969    }
33970
33971    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
33972        // SEARCH kind FIRST BY this SET expression [USING using]
33973        // or CYCLE this SET expression [USING using]
33974        if e.kind == "CYCLE" {
33975            self.write_keyword("CYCLE");
33976        } else {
33977            self.write_keyword("SEARCH");
33978            self.write_space();
33979            self.write(&e.kind);
33980            self.write_space();
33981            self.write_keyword("FIRST BY");
33982        }
33983        self.write_space();
33984        self.generate_expression(&e.this)?;
33985        self.write_space();
33986        self.write_keyword("SET");
33987        self.write_space();
33988        self.generate_expression(&e.expression)?;
33989        if let Some(using) = &e.using {
33990            self.write_space();
33991            self.write_keyword("USING");
33992            self.write_space();
33993            self.generate_expression(using)?;
33994        }
33995        Ok(())
33996    }
33997
33998    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
33999        // REDUCE(this, initial, merge, [finish])
34000        self.write_keyword("REDUCE");
34001        self.write("(");
34002        self.generate_expression(&e.this)?;
34003        if let Some(initial) = &e.initial {
34004            self.write(", ");
34005            self.generate_expression(initial)?;
34006        }
34007        if let Some(merge) = &e.merge {
34008            self.write(", ");
34009            self.generate_expression(merge)?;
34010        }
34011        if let Some(finish) = &e.finish {
34012            self.write(", ");
34013            self.generate_expression(finish)?;
34014        }
34015        self.write(")");
34016        Ok(())
34017    }
34018
34019    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
34020        // REFERENCES this (expressions) [options]
34021        self.write_keyword("REFERENCES");
34022        self.write_space();
34023        self.generate_expression(&e.this)?;
34024        if !e.expressions.is_empty() {
34025            self.write(" (");
34026            for (i, expr) in e.expressions.iter().enumerate() {
34027                if i > 0 {
34028                    self.write(", ");
34029                }
34030                self.generate_expression(expr)?;
34031            }
34032            self.write(")");
34033        }
34034        for opt in &e.options {
34035            self.write_space();
34036            self.generate_expression(opt)?;
34037        }
34038        Ok(())
34039    }
34040
34041    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
34042        // REFRESH [kind] this
34043        self.write_keyword("REFRESH");
34044        if !e.kind.is_empty() {
34045            self.write_space();
34046            self.write_keyword(&e.kind);
34047        }
34048        self.write_space();
34049        self.generate_expression(&e.this)?;
34050        Ok(())
34051    }
34052
34053    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
34054        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
34055        self.write_keyword("REFRESH");
34056        self.write_space();
34057        self.write_keyword(&e.method);
34058
34059        if let Some(ref kind) = e.kind {
34060            self.write_space();
34061            self.write_keyword("ON");
34062            self.write_space();
34063            self.write_keyword(kind);
34064
34065            // EVERY n UNIT
34066            if let Some(ref every) = e.every {
34067                self.write_space();
34068                self.write_keyword("EVERY");
34069                self.write_space();
34070                self.generate_expression(every)?;
34071                if let Some(ref unit) = e.unit {
34072                    self.write_space();
34073                    self.write_keyword(unit);
34074                }
34075            }
34076
34077            // STARTS 'datetime'
34078            if let Some(ref starts) = e.starts {
34079                self.write_space();
34080                self.write_keyword("STARTS");
34081                self.write_space();
34082                self.generate_expression(starts)?;
34083            }
34084        }
34085        Ok(())
34086    }
34087
34088    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
34089        // REGEXP_COUNT(this, expression, position, parameters)
34090        self.write_keyword("REGEXP_COUNT");
34091        self.write("(");
34092        self.generate_expression(&e.this)?;
34093        self.write(", ");
34094        self.generate_expression(&e.expression)?;
34095        if let Some(position) = &e.position {
34096            self.write(", ");
34097            self.generate_expression(position)?;
34098        }
34099        if let Some(parameters) = &e.parameters {
34100            self.write(", ");
34101            self.generate_expression(parameters)?;
34102        }
34103        self.write(")");
34104        Ok(())
34105    }
34106
34107    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
34108        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
34109        self.write_keyword("REGEXP_EXTRACT_ALL");
34110        self.write("(");
34111        self.generate_expression(&e.this)?;
34112        self.write(", ");
34113        self.generate_expression(&e.expression)?;
34114        if let Some(group) = &e.group {
34115            self.write(", ");
34116            self.generate_expression(group)?;
34117        }
34118        self.write(")");
34119        Ok(())
34120    }
34121
34122    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
34123        // REGEXP_FULL_MATCH(this, expression)
34124        self.write_keyword("REGEXP_FULL_MATCH");
34125        self.write("(");
34126        self.generate_expression(&e.this)?;
34127        self.write(", ");
34128        self.generate_expression(&e.expression)?;
34129        self.write(")");
34130        Ok(())
34131    }
34132
34133    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
34134        use crate::dialects::DialectType;
34135        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
34136        if matches!(
34137            self.config.dialect,
34138            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
34139        ) && e.flag.is_none()
34140        {
34141            self.generate_expression(&e.this)?;
34142            self.write(" ~* ");
34143            self.generate_expression(&e.expression)?;
34144        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34145            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
34146            self.write_keyword("REGEXP_LIKE");
34147            self.write("(");
34148            self.generate_expression(&e.this)?;
34149            self.write(", ");
34150            self.generate_expression(&e.expression)?;
34151            self.write(", ");
34152            if let Some(flag) = &e.flag {
34153                self.generate_expression(flag)?;
34154            } else {
34155                self.write("'i'");
34156            }
34157            self.write(")");
34158        } else {
34159            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
34160            self.generate_expression(&e.this)?;
34161            self.write_space();
34162            self.write_keyword("REGEXP_ILIKE");
34163            self.write_space();
34164            self.generate_expression(&e.expression)?;
34165            if let Some(flag) = &e.flag {
34166                self.write(", ");
34167                self.generate_expression(flag)?;
34168            }
34169        }
34170        Ok(())
34171    }
34172
34173    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
34174        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
34175        self.write_keyword("REGEXP_INSTR");
34176        self.write("(");
34177        self.generate_expression(&e.this)?;
34178        self.write(", ");
34179        self.generate_expression(&e.expression)?;
34180        if let Some(position) = &e.position {
34181            self.write(", ");
34182            self.generate_expression(position)?;
34183        }
34184        if let Some(occurrence) = &e.occurrence {
34185            self.write(", ");
34186            self.generate_expression(occurrence)?;
34187        }
34188        if let Some(option) = &e.option {
34189            self.write(", ");
34190            self.generate_expression(option)?;
34191        }
34192        if let Some(parameters) = &e.parameters {
34193            self.write(", ");
34194            self.generate_expression(parameters)?;
34195        }
34196        if let Some(group) = &e.group {
34197            self.write(", ");
34198            self.generate_expression(group)?;
34199        }
34200        self.write(")");
34201        Ok(())
34202    }
34203
34204    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
34205        // REGEXP_SPLIT(this, expression, limit)
34206        self.write_keyword("REGEXP_SPLIT");
34207        self.write("(");
34208        self.generate_expression(&e.this)?;
34209        self.write(", ");
34210        self.generate_expression(&e.expression)?;
34211        if let Some(limit) = &e.limit {
34212            self.write(", ");
34213            self.generate_expression(limit)?;
34214        }
34215        self.write(")");
34216        Ok(())
34217    }
34218
34219    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
34220        // REGR_AVGX(this, expression)
34221        self.write_keyword("REGR_AVGX");
34222        self.write("(");
34223        self.generate_expression(&e.this)?;
34224        self.write(", ");
34225        self.generate_expression(&e.expression)?;
34226        self.write(")");
34227        Ok(())
34228    }
34229
34230    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
34231        // REGR_AVGY(this, expression)
34232        self.write_keyword("REGR_AVGY");
34233        self.write("(");
34234        self.generate_expression(&e.this)?;
34235        self.write(", ");
34236        self.generate_expression(&e.expression)?;
34237        self.write(")");
34238        Ok(())
34239    }
34240
34241    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
34242        // REGR_COUNT(this, expression)
34243        self.write_keyword("REGR_COUNT");
34244        self.write("(");
34245        self.generate_expression(&e.this)?;
34246        self.write(", ");
34247        self.generate_expression(&e.expression)?;
34248        self.write(")");
34249        Ok(())
34250    }
34251
34252    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
34253        // REGR_INTERCEPT(this, expression)
34254        self.write_keyword("REGR_INTERCEPT");
34255        self.write("(");
34256        self.generate_expression(&e.this)?;
34257        self.write(", ");
34258        self.generate_expression(&e.expression)?;
34259        self.write(")");
34260        Ok(())
34261    }
34262
34263    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
34264        // REGR_R2(this, expression)
34265        self.write_keyword("REGR_R2");
34266        self.write("(");
34267        self.generate_expression(&e.this)?;
34268        self.write(", ");
34269        self.generate_expression(&e.expression)?;
34270        self.write(")");
34271        Ok(())
34272    }
34273
34274    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
34275        // REGR_SLOPE(this, expression)
34276        self.write_keyword("REGR_SLOPE");
34277        self.write("(");
34278        self.generate_expression(&e.this)?;
34279        self.write(", ");
34280        self.generate_expression(&e.expression)?;
34281        self.write(")");
34282        Ok(())
34283    }
34284
34285    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
34286        // REGR_SXX(this, expression)
34287        self.write_keyword("REGR_SXX");
34288        self.write("(");
34289        self.generate_expression(&e.this)?;
34290        self.write(", ");
34291        self.generate_expression(&e.expression)?;
34292        self.write(")");
34293        Ok(())
34294    }
34295
34296    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
34297        // REGR_SXY(this, expression)
34298        self.write_keyword("REGR_SXY");
34299        self.write("(");
34300        self.generate_expression(&e.this)?;
34301        self.write(", ");
34302        self.generate_expression(&e.expression)?;
34303        self.write(")");
34304        Ok(())
34305    }
34306
34307    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
34308        // REGR_SYY(this, expression)
34309        self.write_keyword("REGR_SYY");
34310        self.write("(");
34311        self.generate_expression(&e.this)?;
34312        self.write(", ");
34313        self.generate_expression(&e.expression)?;
34314        self.write(")");
34315        Ok(())
34316    }
34317
34318    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
34319        // REGR_VALX(this, expression)
34320        self.write_keyword("REGR_VALX");
34321        self.write("(");
34322        self.generate_expression(&e.this)?;
34323        self.write(", ");
34324        self.generate_expression(&e.expression)?;
34325        self.write(")");
34326        Ok(())
34327    }
34328
34329    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
34330        // REGR_VALY(this, expression)
34331        self.write_keyword("REGR_VALY");
34332        self.write("(");
34333        self.generate_expression(&e.this)?;
34334        self.write(", ");
34335        self.generate_expression(&e.expression)?;
34336        self.write(")");
34337        Ok(())
34338    }
34339
34340    fn generate_remote_with_connection_model_property(
34341        &mut self,
34342        e: &RemoteWithConnectionModelProperty,
34343    ) -> Result<()> {
34344        // REMOTE WITH CONNECTION this
34345        self.write_keyword("REMOTE WITH CONNECTION");
34346        self.write_space();
34347        self.generate_expression(&e.this)?;
34348        Ok(())
34349    }
34350
34351    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
34352        // RENAME COLUMN [IF EXISTS] this TO new_name
34353        self.write_keyword("RENAME COLUMN");
34354        if e.exists {
34355            self.write_space();
34356            self.write_keyword("IF EXISTS");
34357        }
34358        self.write_space();
34359        self.generate_expression(&e.this)?;
34360        if let Some(to) = &e.to {
34361            self.write_space();
34362            self.write_keyword("TO");
34363            self.write_space();
34364            self.generate_expression(to)?;
34365        }
34366        Ok(())
34367    }
34368
34369    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
34370        // REPLACE PARTITION expression [FROM source]
34371        self.write_keyword("REPLACE PARTITION");
34372        self.write_space();
34373        self.generate_expression(&e.expression)?;
34374        if let Some(source) = &e.source {
34375            self.write_space();
34376            self.write_keyword("FROM");
34377            self.write_space();
34378            self.generate_expression(source)?;
34379        }
34380        Ok(())
34381    }
34382
34383    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
34384        // RETURNING expressions [INTO into]
34385        // TSQL and Fabric use OUTPUT instead of RETURNING
34386        let keyword = match self.config.dialect {
34387            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
34388            _ => "RETURNING",
34389        };
34390        self.write_keyword(keyword);
34391        self.write_space();
34392        for (i, expr) in e.expressions.iter().enumerate() {
34393            if i > 0 {
34394                self.write(", ");
34395            }
34396            self.generate_expression(expr)?;
34397        }
34398        if let Some(into) = &e.into {
34399            self.write_space();
34400            self.write_keyword("INTO");
34401            self.write_space();
34402            self.generate_expression(into)?;
34403        }
34404        Ok(())
34405    }
34406
34407    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
34408        // OUTPUT expressions [INTO into_table]
34409        self.write_space();
34410        self.write_keyword("OUTPUT");
34411        self.write_space();
34412        for (i, expr) in output.columns.iter().enumerate() {
34413            if i > 0 {
34414                self.write(", ");
34415            }
34416            self.generate_expression(expr)?;
34417        }
34418        if let Some(into_table) = &output.into_table {
34419            self.write_space();
34420            self.write_keyword("INTO");
34421            self.write_space();
34422            self.generate_expression(into_table)?;
34423        }
34424        Ok(())
34425    }
34426
34427    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
34428        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
34429        self.write_keyword("RETURNS");
34430        if e.is_table.is_some() {
34431            self.write_space();
34432            self.write_keyword("TABLE");
34433        }
34434        if let Some(table) = &e.table {
34435            self.write_space();
34436            self.generate_expression(table)?;
34437        } else if let Some(this) = &e.this {
34438            self.write_space();
34439            self.generate_expression(this)?;
34440        }
34441        if e.null.is_some() {
34442            self.write_space();
34443            self.write_keyword("NULL ON NULL INPUT");
34444        }
34445        Ok(())
34446    }
34447
34448    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
34449        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
34450        self.write_keyword("ROLLBACK");
34451
34452        // TSQL always uses ROLLBACK TRANSACTION
34453        if e.this.is_none()
34454            && matches!(
34455                self.config.dialect,
34456                Some(DialectType::TSQL) | Some(DialectType::Fabric)
34457            )
34458        {
34459            self.write_space();
34460            self.write_keyword("TRANSACTION");
34461        }
34462
34463        // Check if this has TRANSACTION keyword or transaction name
34464        if let Some(this) = &e.this {
34465            // Check if it's just the "TRANSACTION" marker or an actual transaction name
34466            let is_transaction_marker = matches!(
34467                this.as_ref(),
34468                Expression::Identifier(id) if id.name == "TRANSACTION"
34469            );
34470
34471            self.write_space();
34472            self.write_keyword("TRANSACTION");
34473
34474            // If it's a real transaction name, output it
34475            if !is_transaction_marker {
34476                self.write_space();
34477                self.generate_expression(this)?;
34478            }
34479        }
34480
34481        // Output TO savepoint
34482        if let Some(savepoint) = &e.savepoint {
34483            self.write_space();
34484            self.write_keyword("TO");
34485            self.write_space();
34486            self.generate_expression(savepoint)?;
34487        }
34488        Ok(())
34489    }
34490
34491    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
34492        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
34493        if e.expressions.is_empty() {
34494            self.write_keyword("WITH ROLLUP");
34495        } else {
34496            self.write_keyword("ROLLUP");
34497            self.write("(");
34498            for (i, expr) in e.expressions.iter().enumerate() {
34499                if i > 0 {
34500                    self.write(", ");
34501                }
34502                self.generate_expression(expr)?;
34503            }
34504            self.write(")");
34505        }
34506        Ok(())
34507    }
34508
34509    fn generate_row_format_delimited_property(
34510        &mut self,
34511        e: &RowFormatDelimitedProperty,
34512    ) -> Result<()> {
34513        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
34514        self.write_keyword("ROW FORMAT DELIMITED");
34515        if let Some(fields) = &e.fields {
34516            self.write_space();
34517            self.write_keyword("FIELDS TERMINATED BY");
34518            self.write_space();
34519            self.generate_expression(fields)?;
34520        }
34521        if let Some(escaped) = &e.escaped {
34522            self.write_space();
34523            self.write_keyword("ESCAPED BY");
34524            self.write_space();
34525            self.generate_expression(escaped)?;
34526        }
34527        if let Some(items) = &e.collection_items {
34528            self.write_space();
34529            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
34530            self.write_space();
34531            self.generate_expression(items)?;
34532        }
34533        if let Some(keys) = &e.map_keys {
34534            self.write_space();
34535            self.write_keyword("MAP KEYS TERMINATED BY");
34536            self.write_space();
34537            self.generate_expression(keys)?;
34538        }
34539        if let Some(lines) = &e.lines {
34540            self.write_space();
34541            self.write_keyword("LINES TERMINATED BY");
34542            self.write_space();
34543            self.generate_expression(lines)?;
34544        }
34545        if let Some(null) = &e.null {
34546            self.write_space();
34547            self.write_keyword("NULL DEFINED AS");
34548            self.write_space();
34549            self.generate_expression(null)?;
34550        }
34551        if let Some(serde) = &e.serde {
34552            self.write_space();
34553            self.generate_expression(serde)?;
34554        }
34555        Ok(())
34556    }
34557
34558    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
34559        // ROW FORMAT this
34560        self.write_keyword("ROW FORMAT");
34561        self.write_space();
34562        self.generate_expression(&e.this)?;
34563        Ok(())
34564    }
34565
34566    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
34567        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
34568        self.write_keyword("ROW FORMAT SERDE");
34569        self.write_space();
34570        self.generate_expression(&e.this)?;
34571        if let Some(props) = &e.serde_properties {
34572            self.write_space();
34573            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
34574            self.generate_expression(props)?;
34575        }
34576        Ok(())
34577    }
34578
34579    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
34580        // SHA2(this, length)
34581        self.write_keyword("SHA2");
34582        self.write("(");
34583        self.generate_expression(&e.this)?;
34584        if let Some(length) = e.length {
34585            self.write(", ");
34586            self.write(&length.to_string());
34587        }
34588        self.write(")");
34589        Ok(())
34590    }
34591
34592    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
34593        // SHA2_DIGEST(this, length)
34594        self.write_keyword("SHA2_DIGEST");
34595        self.write("(");
34596        self.generate_expression(&e.this)?;
34597        if let Some(length) = e.length {
34598            self.write(", ");
34599            self.write(&length.to_string());
34600        }
34601        self.write(")");
34602        Ok(())
34603    }
34604
34605    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
34606        let name = if matches!(
34607            self.config.dialect,
34608            Some(crate::dialects::DialectType::Spark)
34609                | Some(crate::dialects::DialectType::Databricks)
34610        ) {
34611            "TRY_ADD"
34612        } else {
34613            "SAFE_ADD"
34614        };
34615        self.write_keyword(name);
34616        self.write("(");
34617        self.generate_expression(&e.this)?;
34618        self.write(", ");
34619        self.generate_expression(&e.expression)?;
34620        self.write(")");
34621        Ok(())
34622    }
34623
34624    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
34625        // SAFE_DIVIDE(this, expression)
34626        self.write_keyword("SAFE_DIVIDE");
34627        self.write("(");
34628        self.generate_expression(&e.this)?;
34629        self.write(", ");
34630        self.generate_expression(&e.expression)?;
34631        self.write(")");
34632        Ok(())
34633    }
34634
34635    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
34636        let name = if matches!(
34637            self.config.dialect,
34638            Some(crate::dialects::DialectType::Spark)
34639                | Some(crate::dialects::DialectType::Databricks)
34640        ) {
34641            "TRY_MULTIPLY"
34642        } else {
34643            "SAFE_MULTIPLY"
34644        };
34645        self.write_keyword(name);
34646        self.write("(");
34647        self.generate_expression(&e.this)?;
34648        self.write(", ");
34649        self.generate_expression(&e.expression)?;
34650        self.write(")");
34651        Ok(())
34652    }
34653
34654    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
34655        let name = if matches!(
34656            self.config.dialect,
34657            Some(crate::dialects::DialectType::Spark)
34658                | Some(crate::dialects::DialectType::Databricks)
34659        ) {
34660            "TRY_SUBTRACT"
34661        } else {
34662            "SAFE_SUBTRACT"
34663        };
34664        self.write_keyword(name);
34665        self.write("(");
34666        self.generate_expression(&e.this)?;
34667        self.write(", ");
34668        self.generate_expression(&e.expression)?;
34669        self.write(")");
34670        Ok(())
34671    }
34672
34673    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
34674    /// METHOD (size UNIT) [REPEATABLE (seed)]
34675    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
34676        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
34677        if matches!(sample.method, SampleMethod::Bucket) {
34678            self.write(" (");
34679            self.write_keyword("BUCKET");
34680            self.write_space();
34681            if let Some(ref num) = sample.bucket_numerator {
34682                self.generate_expression(num)?;
34683            }
34684            self.write_space();
34685            self.write_keyword("OUT OF");
34686            self.write_space();
34687            if let Some(ref denom) = sample.bucket_denominator {
34688                self.generate_expression(denom)?;
34689            }
34690            if let Some(ref field) = sample.bucket_field {
34691                self.write_space();
34692                self.write_keyword("ON");
34693                self.write_space();
34694                self.generate_expression(field)?;
34695            }
34696            self.write(")");
34697            return Ok(());
34698        }
34699
34700        // Output method name if explicitly specified, or for dialects that always require it
34701        let is_snowflake = matches!(
34702            self.config.dialect,
34703            Some(crate::dialects::DialectType::Snowflake)
34704        );
34705        let is_postgres = matches!(
34706            self.config.dialect,
34707            Some(crate::dialects::DialectType::PostgreSQL)
34708                | Some(crate::dialects::DialectType::Redshift)
34709        );
34710        // Databricks and Spark don't output method names
34711        let is_databricks = matches!(
34712            self.config.dialect,
34713            Some(crate::dialects::DialectType::Databricks)
34714        );
34715        let is_spark = matches!(
34716            self.config.dialect,
34717            Some(crate::dialects::DialectType::Spark)
34718        );
34719        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
34720        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
34721        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
34722        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
34723            self.write_space();
34724            if !sample.explicit_method && (is_snowflake || force_method) {
34725                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
34726                self.write_keyword("BERNOULLI");
34727            } else {
34728                match sample.method {
34729                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
34730                    SampleMethod::System => self.write_keyword("SYSTEM"),
34731                    SampleMethod::Block => self.write_keyword("BLOCK"),
34732                    SampleMethod::Row => self.write_keyword("ROW"),
34733                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
34734                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
34735                    SampleMethod::Bucket => {} // handled above
34736                }
34737            }
34738        }
34739
34740        // Output size, with or without parentheses depending on dialect
34741        let emit_size_no_parens = !self.config.tablesample_requires_parens;
34742        if emit_size_no_parens {
34743            self.write_space();
34744            match &sample.size {
34745                Expression::Tuple(tuple) => {
34746                    for (i, expr) in tuple.expressions.iter().enumerate() {
34747                        if i > 0 {
34748                            self.write(", ");
34749                        }
34750                        self.generate_expression(expr)?;
34751                    }
34752                }
34753                expr => self.generate_expression(expr)?,
34754            }
34755        } else {
34756            self.write(" (");
34757            self.generate_expression(&sample.size)?;
34758        }
34759
34760        // Determine unit
34761        let is_rows_method = matches!(
34762            sample.method,
34763            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
34764        );
34765        let is_percent = matches!(
34766            sample.method,
34767            SampleMethod::Percent
34768                | SampleMethod::System
34769                | SampleMethod::Bernoulli
34770                | SampleMethod::Block
34771        );
34772
34773        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
34774        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
34775        // For Databricks and Spark, always output PERCENT for percentage samples.
34776        let is_presto = matches!(
34777            self.config.dialect,
34778            Some(crate::dialects::DialectType::Presto)
34779                | Some(crate::dialects::DialectType::Trino)
34780                | Some(crate::dialects::DialectType::Athena)
34781        );
34782        let should_output_unit = if is_databricks || is_spark {
34783            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
34784            is_percent || is_rows_method || sample.unit_after_size
34785        } else if is_snowflake || is_postgres || is_presto {
34786            sample.unit_after_size
34787        } else {
34788            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
34789        };
34790
34791        if should_output_unit {
34792            self.write_space();
34793            if sample.is_percent {
34794                self.write_keyword("PERCENT");
34795            } else if is_rows_method && !sample.unit_after_size {
34796                self.write_keyword("ROWS");
34797            } else if sample.unit_after_size {
34798                match sample.method {
34799                    SampleMethod::Percent
34800                    | SampleMethod::System
34801                    | SampleMethod::Bernoulli
34802                    | SampleMethod::Block => {
34803                        self.write_keyword("PERCENT");
34804                    }
34805                    SampleMethod::Row | SampleMethod::Reservoir => {
34806                        self.write_keyword("ROWS");
34807                    }
34808                    _ => self.write_keyword("ROWS"),
34809                }
34810            } else {
34811                self.write_keyword("PERCENT");
34812            }
34813        }
34814
34815        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34816            if let Some(ref offset) = sample.offset {
34817                self.write_space();
34818                self.write_keyword("OFFSET");
34819                self.write_space();
34820                self.generate_expression(offset)?;
34821            }
34822        }
34823        if !emit_size_no_parens {
34824            self.write(")");
34825        }
34826
34827        Ok(())
34828    }
34829
34830    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
34831        // SAMPLE this (ClickHouse uses SAMPLE BY)
34832        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34833            self.write_keyword("SAMPLE BY");
34834        } else {
34835            self.write_keyword("SAMPLE");
34836        }
34837        self.write_space();
34838        self.generate_expression(&e.this)?;
34839        Ok(())
34840    }
34841
34842    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
34843        // this (expressions...)
34844        if let Some(this) = &e.this {
34845            self.generate_expression(this)?;
34846        }
34847        if !e.expressions.is_empty() {
34848            // Add space before column list if there's a preceding expression
34849            if e.this.is_some() {
34850                self.write_space();
34851            }
34852            self.write("(");
34853            for (i, expr) in e.expressions.iter().enumerate() {
34854                if i > 0 {
34855                    self.write(", ");
34856                }
34857                self.generate_expression(expr)?;
34858            }
34859            self.write(")");
34860        }
34861        Ok(())
34862    }
34863
34864    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
34865        // COMMENT this
34866        self.write_keyword("COMMENT");
34867        self.write_space();
34868        self.generate_expression(&e.this)?;
34869        Ok(())
34870    }
34871
34872    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
34873        // [this::]expression
34874        if let Some(this) = &e.this {
34875            self.generate_expression(this)?;
34876            self.write("::");
34877        }
34878        self.generate_expression(&e.expression)?;
34879        Ok(())
34880    }
34881
34882    fn generate_search(&mut self, e: &Search) -> Result<()> {
34883        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
34884        self.write_keyword("SEARCH");
34885        self.write("(");
34886        self.generate_expression(&e.this)?;
34887        self.write(", ");
34888        self.generate_expression(&e.expression)?;
34889        if let Some(json_scope) = &e.json_scope {
34890            self.write(", ");
34891            self.generate_expression(json_scope)?;
34892        }
34893        if let Some(analyzer) = &e.analyzer {
34894            self.write(", ");
34895            self.generate_expression(analyzer)?;
34896        }
34897        if let Some(analyzer_options) = &e.analyzer_options {
34898            self.write(", ");
34899            self.generate_expression(analyzer_options)?;
34900        }
34901        if let Some(search_mode) = &e.search_mode {
34902            self.write(", ");
34903            self.generate_expression(search_mode)?;
34904        }
34905        self.write(")");
34906        Ok(())
34907    }
34908
34909    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
34910        // SEARCH_IP(this, expression)
34911        self.write_keyword("SEARCH_IP");
34912        self.write("(");
34913        self.generate_expression(&e.this)?;
34914        self.write(", ");
34915        self.generate_expression(&e.expression)?;
34916        self.write(")");
34917        Ok(())
34918    }
34919
34920    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
34921        // SECURITY this
34922        self.write_keyword("SECURITY");
34923        self.write_space();
34924        self.generate_expression(&e.this)?;
34925        Ok(())
34926    }
34927
34928    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
34929        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
34930        self.write("SEMANTIC_VIEW(");
34931
34932        if self.config.pretty {
34933            // Pretty print: each clause on its own line
34934            self.write_newline();
34935            self.indent_level += 1;
34936            self.write_indent();
34937            self.generate_expression(&e.this)?;
34938
34939            if let Some(metrics) = &e.metrics {
34940                self.write_newline();
34941                self.write_indent();
34942                self.write_keyword("METRICS");
34943                self.write_space();
34944                self.generate_semantic_view_tuple(metrics)?;
34945            }
34946            if let Some(dimensions) = &e.dimensions {
34947                self.write_newline();
34948                self.write_indent();
34949                self.write_keyword("DIMENSIONS");
34950                self.write_space();
34951                self.generate_semantic_view_tuple(dimensions)?;
34952            }
34953            if let Some(facts) = &e.facts {
34954                self.write_newline();
34955                self.write_indent();
34956                self.write_keyword("FACTS");
34957                self.write_space();
34958                self.generate_semantic_view_tuple(facts)?;
34959            }
34960            if let Some(where_) = &e.where_ {
34961                self.write_newline();
34962                self.write_indent();
34963                self.write_keyword("WHERE");
34964                self.write_space();
34965                self.generate_expression(where_)?;
34966            }
34967            self.write_newline();
34968            self.indent_level -= 1;
34969            self.write_indent();
34970        } else {
34971            // Compact: all on one line
34972            self.generate_expression(&e.this)?;
34973            if let Some(metrics) = &e.metrics {
34974                self.write_space();
34975                self.write_keyword("METRICS");
34976                self.write_space();
34977                self.generate_semantic_view_tuple(metrics)?;
34978            }
34979            if let Some(dimensions) = &e.dimensions {
34980                self.write_space();
34981                self.write_keyword("DIMENSIONS");
34982                self.write_space();
34983                self.generate_semantic_view_tuple(dimensions)?;
34984            }
34985            if let Some(facts) = &e.facts {
34986                self.write_space();
34987                self.write_keyword("FACTS");
34988                self.write_space();
34989                self.generate_semantic_view_tuple(facts)?;
34990            }
34991            if let Some(where_) = &e.where_ {
34992                self.write_space();
34993                self.write_keyword("WHERE");
34994                self.write_space();
34995                self.generate_expression(where_)?;
34996            }
34997        }
34998        self.write(")");
34999        Ok(())
35000    }
35001
35002    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
35003    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
35004        if let Expression::Tuple(t) = expr {
35005            for (i, e) in t.expressions.iter().enumerate() {
35006                if i > 0 {
35007                    self.write(", ");
35008                }
35009                self.generate_expression(e)?;
35010            }
35011        } else {
35012            self.generate_expression(expr)?;
35013        }
35014        Ok(())
35015    }
35016
35017    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
35018        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
35019        if let Some(start) = &e.start {
35020            self.write_keyword("START WITH");
35021            self.write_space();
35022            self.generate_expression(start)?;
35023        }
35024        if let Some(increment) = &e.increment {
35025            self.write_space();
35026            self.write_keyword("INCREMENT BY");
35027            self.write_space();
35028            self.generate_expression(increment)?;
35029        }
35030        if let Some(minvalue) = &e.minvalue {
35031            self.write_space();
35032            self.write_keyword("MINVALUE");
35033            self.write_space();
35034            self.generate_expression(minvalue)?;
35035        }
35036        if let Some(maxvalue) = &e.maxvalue {
35037            self.write_space();
35038            self.write_keyword("MAXVALUE");
35039            self.write_space();
35040            self.generate_expression(maxvalue)?;
35041        }
35042        if let Some(cache) = &e.cache {
35043            self.write_space();
35044            self.write_keyword("CACHE");
35045            self.write_space();
35046            self.generate_expression(cache)?;
35047        }
35048        if let Some(owned) = &e.owned {
35049            self.write_space();
35050            self.write_keyword("OWNED BY");
35051            self.write_space();
35052            self.generate_expression(owned)?;
35053        }
35054        for opt in &e.options {
35055            self.write_space();
35056            self.generate_expression(opt)?;
35057        }
35058        Ok(())
35059    }
35060
35061    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
35062        // [WITH] SERDEPROPERTIES (expressions)
35063        if e.with_.is_some() {
35064            self.write_keyword("WITH");
35065            self.write_space();
35066        }
35067        self.write_keyword("SERDEPROPERTIES");
35068        self.write(" (");
35069        for (i, expr) in e.expressions.iter().enumerate() {
35070            if i > 0 {
35071                self.write(", ");
35072            }
35073            // Generate key=value without spaces around =
35074            match expr {
35075                Expression::Eq(eq) => {
35076                    self.generate_expression(&eq.left)?;
35077                    self.write("=");
35078                    self.generate_expression(&eq.right)?;
35079                }
35080                _ => self.generate_expression(expr)?,
35081            }
35082        }
35083        self.write(")");
35084        Ok(())
35085    }
35086
35087    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
35088        // @@[kind.]this
35089        self.write("@@");
35090        if let Some(kind) = &e.kind {
35091            self.write(kind);
35092            self.write(".");
35093        }
35094        self.generate_expression(&e.this)?;
35095        Ok(())
35096    }
35097
35098    fn generate_set(&mut self, e: &Set) -> Result<()> {
35099        // SET/UNSET [TAG] expressions
35100        if e.unset.is_some() {
35101            self.write_keyword("UNSET");
35102        } else {
35103            self.write_keyword("SET");
35104        }
35105        if e.tag.is_some() {
35106            self.write_space();
35107            self.write_keyword("TAG");
35108        }
35109        if !e.expressions.is_empty() {
35110            self.write_space();
35111            for (i, expr) in e.expressions.iter().enumerate() {
35112                if i > 0 {
35113                    self.write(", ");
35114                }
35115                self.generate_expression(expr)?;
35116            }
35117        }
35118        Ok(())
35119    }
35120
35121    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
35122        // SET this or SETCONFIG this
35123        self.write_keyword("SET");
35124        self.write_space();
35125        self.generate_expression(&e.this)?;
35126        Ok(())
35127    }
35128
35129    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
35130        // [kind] name = value
35131        if let Some(kind) = &e.kind {
35132            self.write_keyword(kind);
35133            self.write_space();
35134        }
35135        self.generate_expression(&e.name)?;
35136        self.write(" = ");
35137        self.generate_expression(&e.value)?;
35138        Ok(())
35139    }
35140
35141    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
35142        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
35143        if let Some(with_) = &e.with_ {
35144            self.generate_expression(with_)?;
35145            self.write_space();
35146        }
35147        self.generate_expression(&e.this)?;
35148        self.write_space();
35149        // kind should be UNION, INTERSECT, EXCEPT, etc.
35150        if let Some(kind) = &e.kind {
35151            self.write_keyword(kind);
35152        }
35153        if e.distinct {
35154            self.write_space();
35155            self.write_keyword("DISTINCT");
35156        } else {
35157            self.write_space();
35158            self.write_keyword("ALL");
35159        }
35160        if e.by_name.is_some() {
35161            self.write_space();
35162            self.write_keyword("BY NAME");
35163        }
35164        self.write_space();
35165        self.generate_expression(&e.expression)?;
35166        Ok(())
35167    }
35168
35169    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
35170        // SET or MULTISET
35171        if e.multi.is_some() {
35172            self.write_keyword("MULTISET");
35173        } else {
35174            self.write_keyword("SET");
35175        }
35176        Ok(())
35177    }
35178
35179    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
35180        // SETTINGS expressions
35181        self.write_keyword("SETTINGS");
35182        if self.config.pretty && e.expressions.len() > 1 {
35183            // Pretty print: each setting on its own line, indented
35184            self.indent_level += 1;
35185            for (i, expr) in e.expressions.iter().enumerate() {
35186                if i > 0 {
35187                    self.write(",");
35188                }
35189                self.write_newline();
35190                self.write_indent();
35191                self.generate_expression(expr)?;
35192            }
35193            self.indent_level -= 1;
35194        } else {
35195            self.write_space();
35196            for (i, expr) in e.expressions.iter().enumerate() {
35197                if i > 0 {
35198                    self.write(", ");
35199                }
35200                self.generate_expression(expr)?;
35201            }
35202        }
35203        Ok(())
35204    }
35205
35206    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
35207        // SHARING = this
35208        self.write_keyword("SHARING");
35209        if let Some(this) = &e.this {
35210            self.write(" = ");
35211            self.generate_expression(this)?;
35212        }
35213        Ok(())
35214    }
35215
35216    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
35217        // Python array slicing: begin:end:step
35218        if let Some(begin) = &e.this {
35219            self.generate_expression(begin)?;
35220        }
35221        self.write(":");
35222        if let Some(end) = &e.expression {
35223            self.generate_expression(end)?;
35224        }
35225        if let Some(step) = &e.step {
35226            self.write(":");
35227            self.generate_expression(step)?;
35228        }
35229        Ok(())
35230    }
35231
35232    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
35233        // SORT_ARRAY(this, asc)
35234        self.write_keyword("SORT_ARRAY");
35235        self.write("(");
35236        self.generate_expression(&e.this)?;
35237        if let Some(asc) = &e.asc {
35238            self.write(", ");
35239            self.generate_expression(asc)?;
35240        }
35241        self.write(")");
35242        Ok(())
35243    }
35244
35245    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
35246        // SORT BY expressions
35247        self.write_keyword("SORT BY");
35248        self.write_space();
35249        for (i, expr) in e.expressions.iter().enumerate() {
35250            if i > 0 {
35251                self.write(", ");
35252            }
35253            self.generate_ordered(expr)?;
35254        }
35255        Ok(())
35256    }
35257
35258    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
35259        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
35260        if e.compound.is_some() {
35261            self.write_keyword("COMPOUND");
35262            self.write_space();
35263        }
35264        self.write_keyword("SORTKEY");
35265        self.write("(");
35266        // If this is a Tuple, unwrap its contents to avoid double parentheses
35267        if let Expression::Tuple(t) = e.this.as_ref() {
35268            for (i, expr) in t.expressions.iter().enumerate() {
35269                if i > 0 {
35270                    self.write(", ");
35271                }
35272                self.generate_expression(expr)?;
35273            }
35274        } else {
35275            self.generate_expression(&e.this)?;
35276        }
35277        self.write(")");
35278        Ok(())
35279    }
35280
35281    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
35282        // SPLIT_PART(this, delimiter, part_index)
35283        self.write_keyword("SPLIT_PART");
35284        self.write("(");
35285        self.generate_expression(&e.this)?;
35286        if let Some(delimiter) = &e.delimiter {
35287            self.write(", ");
35288            self.generate_expression(delimiter)?;
35289        }
35290        if let Some(part_index) = &e.part_index {
35291            self.write(", ");
35292            self.generate_expression(part_index)?;
35293        }
35294        self.write(")");
35295        Ok(())
35296    }
35297
35298    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
35299        // READS SQL DATA or MODIFIES SQL DATA, etc.
35300        self.generate_expression(&e.this)?;
35301        Ok(())
35302    }
35303
35304    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
35305        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
35306        self.write_keyword("SQL SECURITY");
35307        self.write_space();
35308        self.generate_expression(&e.this)?;
35309        Ok(())
35310    }
35311
35312    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
35313        // ST_DISTANCE(this, expression, [use_spheroid])
35314        self.write_keyword("ST_DISTANCE");
35315        self.write("(");
35316        self.generate_expression(&e.this)?;
35317        self.write(", ");
35318        self.generate_expression(&e.expression)?;
35319        if let Some(use_spheroid) = &e.use_spheroid {
35320            self.write(", ");
35321            self.generate_expression(use_spheroid)?;
35322        }
35323        self.write(")");
35324        Ok(())
35325    }
35326
35327    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
35328        // ST_POINT(this, expression)
35329        self.write_keyword("ST_POINT");
35330        self.write("(");
35331        self.generate_expression(&e.this)?;
35332        self.write(", ");
35333        self.generate_expression(&e.expression)?;
35334        self.write(")");
35335        Ok(())
35336    }
35337
35338    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
35339        // IMMUTABLE, STABLE, VOLATILE
35340        self.generate_expression(&e.this)?;
35341        Ok(())
35342    }
35343
35344    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
35345        // STANDARD_HASH(this, [expression])
35346        self.write_keyword("STANDARD_HASH");
35347        self.write("(");
35348        self.generate_expression(&e.this)?;
35349        if let Some(expression) = &e.expression {
35350            self.write(", ");
35351            self.generate_expression(expression)?;
35352        }
35353        self.write(")");
35354        Ok(())
35355    }
35356
35357    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
35358        // STORED BY this
35359        self.write_keyword("STORED BY");
35360        self.write_space();
35361        self.generate_expression(&e.this)?;
35362        Ok(())
35363    }
35364
35365    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
35366        // STRPOS(this, substr) or STRPOS(this, substr, position)
35367        // Different dialects have different function names
35368        use crate::dialects::DialectType;
35369        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35370            // Snowflake: CHARINDEX(substr, str[, position])
35371            self.write_keyword("CHARINDEX");
35372            self.write("(");
35373            if let Some(substr) = &e.substr {
35374                self.generate_expression(substr)?;
35375                self.write(", ");
35376            }
35377            self.generate_expression(&e.this)?;
35378            if let Some(position) = &e.position {
35379                self.write(", ");
35380                self.generate_expression(position)?;
35381            }
35382            self.write(")");
35383        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
35384            self.write_keyword("POSITION");
35385            self.write("(");
35386            self.generate_expression(&e.this)?;
35387            if let Some(substr) = &e.substr {
35388                self.write(", ");
35389                self.generate_expression(substr)?;
35390            }
35391            if let Some(position) = &e.position {
35392                self.write(", ");
35393                self.generate_expression(position)?;
35394            }
35395            if let Some(occurrence) = &e.occurrence {
35396                self.write(", ");
35397                self.generate_expression(occurrence)?;
35398            }
35399            self.write(")");
35400        } else if matches!(
35401            self.config.dialect,
35402            Some(DialectType::SQLite)
35403                | Some(DialectType::Oracle)
35404                | Some(DialectType::BigQuery)
35405                | Some(DialectType::Teradata)
35406        ) {
35407            self.write_keyword("INSTR");
35408            self.write("(");
35409            self.generate_expression(&e.this)?;
35410            if let Some(substr) = &e.substr {
35411                self.write(", ");
35412                self.generate_expression(substr)?;
35413            }
35414            if let Some(position) = &e.position {
35415                self.write(", ");
35416                self.generate_expression(position)?;
35417            } else if e.occurrence.is_some() {
35418                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
35419                // Default start position is 1
35420                self.write(", 1");
35421            }
35422            if let Some(occurrence) = &e.occurrence {
35423                self.write(", ");
35424                self.generate_expression(occurrence)?;
35425            }
35426            self.write(")");
35427        } else if matches!(
35428            self.config.dialect,
35429            Some(DialectType::MySQL)
35430                | Some(DialectType::SingleStore)
35431                | Some(DialectType::Doris)
35432                | Some(DialectType::StarRocks)
35433                | Some(DialectType::Hive)
35434                | Some(DialectType::Spark)
35435                | Some(DialectType::Databricks)
35436        ) {
35437            // LOCATE(substr, str[, position]) - substr first
35438            self.write_keyword("LOCATE");
35439            self.write("(");
35440            if let Some(substr) = &e.substr {
35441                self.generate_expression(substr)?;
35442                self.write(", ");
35443            }
35444            self.generate_expression(&e.this)?;
35445            if let Some(position) = &e.position {
35446                self.write(", ");
35447                self.generate_expression(position)?;
35448            }
35449            self.write(")");
35450        } else if matches!(
35451            self.config.dialect,
35452            Some(DialectType::TSQL) | Some(DialectType::Fabric)
35453        ) {
35454            // CHARINDEX(substr, str[, position])
35455            self.write_keyword("CHARINDEX");
35456            self.write("(");
35457            if let Some(substr) = &e.substr {
35458                self.generate_expression(substr)?;
35459                self.write(", ");
35460            }
35461            self.generate_expression(&e.this)?;
35462            if let Some(position) = &e.position {
35463                self.write(", ");
35464                self.generate_expression(position)?;
35465            }
35466            self.write(")");
35467        } else if matches!(
35468            self.config.dialect,
35469            Some(DialectType::PostgreSQL)
35470                | Some(DialectType::Materialize)
35471                | Some(DialectType::RisingWave)
35472                | Some(DialectType::Redshift)
35473        ) {
35474            // POSITION(substr IN str) syntax
35475            self.write_keyword("POSITION");
35476            self.write("(");
35477            if let Some(substr) = &e.substr {
35478                self.generate_expression(substr)?;
35479                self.write(" IN ");
35480            }
35481            self.generate_expression(&e.this)?;
35482            self.write(")");
35483        } else {
35484            self.write_keyword("STRPOS");
35485            self.write("(");
35486            self.generate_expression(&e.this)?;
35487            if let Some(substr) = &e.substr {
35488                self.write(", ");
35489                self.generate_expression(substr)?;
35490            }
35491            if let Some(position) = &e.position {
35492                self.write(", ");
35493                self.generate_expression(position)?;
35494            }
35495            if let Some(occurrence) = &e.occurrence {
35496                self.write(", ");
35497                self.generate_expression(occurrence)?;
35498            }
35499            self.write(")");
35500        }
35501        Ok(())
35502    }
35503
35504    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
35505        match self.config.dialect {
35506            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
35507                // TO_DATE(this, java_format)
35508                self.write_keyword("TO_DATE");
35509                self.write("(");
35510                self.generate_expression(&e.this)?;
35511                if let Some(format) = &e.format {
35512                    self.write(", '");
35513                    self.write(&Self::strftime_to_java_format(format));
35514                    self.write("'");
35515                }
35516                self.write(")");
35517            }
35518            Some(DialectType::DuckDB) => {
35519                // CAST(STRPTIME(this, format) AS DATE)
35520                self.write_keyword("CAST");
35521                self.write("(");
35522                self.write_keyword("STRPTIME");
35523                self.write("(");
35524                self.generate_expression(&e.this)?;
35525                if let Some(format) = &e.format {
35526                    self.write(", '");
35527                    self.write(format);
35528                    self.write("'");
35529                }
35530                self.write(")");
35531                self.write_keyword(" AS ");
35532                self.write_keyword("DATE");
35533                self.write(")");
35534            }
35535            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
35536                // TO_DATE(this, pg_format)
35537                self.write_keyword("TO_DATE");
35538                self.write("(");
35539                self.generate_expression(&e.this)?;
35540                if let Some(format) = &e.format {
35541                    self.write(", '");
35542                    self.write(&Self::strftime_to_postgres_format(format));
35543                    self.write("'");
35544                }
35545                self.write(")");
35546            }
35547            Some(DialectType::BigQuery) => {
35548                // PARSE_DATE(format, this) - note: format comes first for BigQuery
35549                self.write_keyword("PARSE_DATE");
35550                self.write("(");
35551                if let Some(format) = &e.format {
35552                    self.write("'");
35553                    self.write(format);
35554                    self.write("'");
35555                    self.write(", ");
35556                }
35557                self.generate_expression(&e.this)?;
35558                self.write(")");
35559            }
35560            Some(DialectType::Teradata) => {
35561                // CAST(this AS DATE FORMAT 'teradata_fmt')
35562                self.write_keyword("CAST");
35563                self.write("(");
35564                self.generate_expression(&e.this)?;
35565                self.write_keyword(" AS ");
35566                self.write_keyword("DATE");
35567                if let Some(format) = &e.format {
35568                    self.write_keyword(" FORMAT ");
35569                    self.write("'");
35570                    self.write(&Self::strftime_to_teradata_format(format));
35571                    self.write("'");
35572                }
35573                self.write(")");
35574            }
35575            _ => {
35576                // STR_TO_DATE(this, format) - MySQL default
35577                self.write_keyword("STR_TO_DATE");
35578                self.write("(");
35579                self.generate_expression(&e.this)?;
35580                if let Some(format) = &e.format {
35581                    self.write(", '");
35582                    self.write(format);
35583                    self.write("'");
35584                }
35585                self.write(")");
35586            }
35587        }
35588        Ok(())
35589    }
35590
35591    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
35592    fn strftime_to_teradata_format(fmt: &str) -> String {
35593        let mut result = String::with_capacity(fmt.len() * 2);
35594        let bytes = fmt.as_bytes();
35595        let len = bytes.len();
35596        let mut i = 0;
35597        while i < len {
35598            if bytes[i] == b'%' && i + 1 < len {
35599                let replacement = match bytes[i + 1] {
35600                    b'Y' => "YYYY",
35601                    b'y' => "YY",
35602                    b'm' => "MM",
35603                    b'B' => "MMMM",
35604                    b'b' => "MMM",
35605                    b'd' => "DD",
35606                    b'j' => "DDD",
35607                    b'H' => "HH",
35608                    b'M' => "MI",
35609                    b'S' => "SS",
35610                    b'f' => "SSSSSS",
35611                    b'A' => "EEEE",
35612                    b'a' => "EEE",
35613                    _ => {
35614                        result.push('%');
35615                        i += 1;
35616                        continue;
35617                    }
35618                };
35619                result.push_str(replacement);
35620                i += 2;
35621            } else {
35622                result.push(bytes[i] as char);
35623                i += 1;
35624            }
35625        }
35626        result
35627    }
35628
35629    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
35630    /// Public static version for use by other modules
35631    pub fn strftime_to_java_format_static(fmt: &str) -> String {
35632        Self::strftime_to_java_format(fmt)
35633    }
35634
35635    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
35636    fn strftime_to_java_format(fmt: &str) -> String {
35637        let mut result = String::with_capacity(fmt.len() * 2);
35638        let bytes = fmt.as_bytes();
35639        let len = bytes.len();
35640        let mut i = 0;
35641        while i < len {
35642            if bytes[i] == b'%' && i + 1 < len {
35643                // Check for non-padded variants (%-X)
35644                if bytes[i + 1] == b'-' && i + 2 < len {
35645                    let replacement = match bytes[i + 2] {
35646                        b'd' => "d",
35647                        b'm' => "M",
35648                        b'H' => "H",
35649                        b'M' => "m",
35650                        b'S' => "s",
35651                        _ => {
35652                            result.push('%');
35653                            i += 1;
35654                            continue;
35655                        }
35656                    };
35657                    result.push_str(replacement);
35658                    i += 3;
35659                } else {
35660                    let replacement = match bytes[i + 1] {
35661                        b'Y' => "yyyy",
35662                        b'y' => "yy",
35663                        b'm' => "MM",
35664                        b'B' => "MMMM",
35665                        b'b' => "MMM",
35666                        b'd' => "dd",
35667                        b'j' => "DDD",
35668                        b'H' => "HH",
35669                        b'M' => "mm",
35670                        b'S' => "ss",
35671                        b'f' => "SSSSSS",
35672                        b'A' => "EEEE",
35673                        b'a' => "EEE",
35674                        _ => {
35675                            result.push('%');
35676                            i += 1;
35677                            continue;
35678                        }
35679                    };
35680                    result.push_str(replacement);
35681                    i += 2;
35682                }
35683            } else {
35684                result.push(bytes[i] as char);
35685                i += 1;
35686            }
35687        }
35688        result
35689    }
35690
35691    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
35692    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
35693    fn strftime_to_tsql_format(fmt: &str) -> String {
35694        let mut result = String::with_capacity(fmt.len() * 2);
35695        let bytes = fmt.as_bytes();
35696        let len = bytes.len();
35697        let mut i = 0;
35698        while i < len {
35699            if bytes[i] == b'%' && i + 1 < len {
35700                // Check for non-padded variants (%-X)
35701                if bytes[i + 1] == b'-' && i + 2 < len {
35702                    let replacement = match bytes[i + 2] {
35703                        b'd' => "d",
35704                        b'm' => "M",
35705                        b'H' => "H",
35706                        b'M' => "m",
35707                        b'S' => "s",
35708                        _ => {
35709                            result.push('%');
35710                            i += 1;
35711                            continue;
35712                        }
35713                    };
35714                    result.push_str(replacement);
35715                    i += 3;
35716                } else {
35717                    let replacement = match bytes[i + 1] {
35718                        b'Y' => "yyyy",
35719                        b'y' => "yy",
35720                        b'm' => "MM",
35721                        b'B' => "MMMM",
35722                        b'b' => "MMM",
35723                        b'd' => "dd",
35724                        b'j' => "DDD",
35725                        b'H' => "HH",
35726                        b'M' => "mm",
35727                        b'S' => "ss",
35728                        b'f' => "ffffff",
35729                        b'A' => "dddd",
35730                        b'a' => "ddd",
35731                        _ => {
35732                            result.push('%');
35733                            i += 1;
35734                            continue;
35735                        }
35736                    };
35737                    result.push_str(replacement);
35738                    i += 2;
35739                }
35740            } else {
35741                result.push(bytes[i] as char);
35742                i += 1;
35743            }
35744        }
35745        result
35746    }
35747
35748    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
35749    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
35750    fn decompose_json_path(path: &str) -> Vec<String> {
35751        let mut parts = Vec::new();
35752        // Strip leading $ and optional .
35753        let path = if path.starts_with("$.") {
35754            &path[2..]
35755        } else if path.starts_with('$') {
35756            &path[1..]
35757        } else {
35758            path
35759        };
35760        if path.is_empty() {
35761            return parts;
35762        }
35763        let mut current = String::new();
35764        let chars: Vec<char> = path.chars().collect();
35765        let mut i = 0;
35766        while i < chars.len() {
35767            match chars[i] {
35768                '.' => {
35769                    if !current.is_empty() {
35770                        parts.push(current.clone());
35771                        current.clear();
35772                    }
35773                    i += 1;
35774                }
35775                '[' => {
35776                    if !current.is_empty() {
35777                        parts.push(current.clone());
35778                        current.clear();
35779                    }
35780                    i += 1;
35781                    // Read the content inside brackets
35782                    let mut bracket_content = String::new();
35783                    while i < chars.len() && chars[i] != ']' {
35784                        // Skip quotes inside brackets
35785                        if chars[i] == '"' || chars[i] == '\'' {
35786                            let quote = chars[i];
35787                            i += 1;
35788                            while i < chars.len() && chars[i] != quote {
35789                                bracket_content.push(chars[i]);
35790                                i += 1;
35791                            }
35792                            if i < chars.len() {
35793                                i += 1;
35794                            } // skip closing quote
35795                        } else {
35796                            bracket_content.push(chars[i]);
35797                            i += 1;
35798                        }
35799                    }
35800                    if i < chars.len() {
35801                        i += 1;
35802                    } // skip ]
35803                      // Skip wildcard [*] - don't add as a part
35804                    if bracket_content != "*" {
35805                        parts.push(bracket_content);
35806                    }
35807                }
35808                _ => {
35809                    current.push(chars[i]);
35810                    i += 1;
35811                }
35812            }
35813        }
35814        if !current.is_empty() {
35815            parts.push(current);
35816        }
35817        parts
35818    }
35819
35820    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
35821    fn strftime_to_postgres_format(fmt: &str) -> String {
35822        let mut result = String::with_capacity(fmt.len() * 2);
35823        let bytes = fmt.as_bytes();
35824        let len = bytes.len();
35825        let mut i = 0;
35826        while i < len {
35827            if bytes[i] == b'%' && i + 1 < len {
35828                // Check for non-padded variants (%-X)
35829                if bytes[i + 1] == b'-' && i + 2 < len {
35830                    let replacement = match bytes[i + 2] {
35831                        b'd' => "FMDD",
35832                        b'm' => "FMMM",
35833                        b'H' => "FMHH24",
35834                        b'M' => "FMMI",
35835                        b'S' => "FMSS",
35836                        _ => {
35837                            result.push('%');
35838                            i += 1;
35839                            continue;
35840                        }
35841                    };
35842                    result.push_str(replacement);
35843                    i += 3;
35844                } else {
35845                    let replacement = match bytes[i + 1] {
35846                        b'Y' => "YYYY",
35847                        b'y' => "YY",
35848                        b'm' => "MM",
35849                        b'B' => "Month",
35850                        b'b' => "Mon",
35851                        b'd' => "DD",
35852                        b'j' => "DDD",
35853                        b'H' => "HH24",
35854                        b'M' => "MI",
35855                        b'S' => "SS",
35856                        b'f' => "US",
35857                        b'A' => "Day",
35858                        b'a' => "Dy",
35859                        _ => {
35860                            result.push('%');
35861                            i += 1;
35862                            continue;
35863                        }
35864                    };
35865                    result.push_str(replacement);
35866                    i += 2;
35867                }
35868            } else {
35869                result.push(bytes[i] as char);
35870                i += 1;
35871            }
35872        }
35873        result
35874    }
35875
35876    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
35877    fn strftime_to_snowflake_format(fmt: &str) -> String {
35878        let mut result = String::with_capacity(fmt.len() * 2);
35879        let bytes = fmt.as_bytes();
35880        let len = bytes.len();
35881        let mut i = 0;
35882        while i < len {
35883            if bytes[i] == b'%' && i + 1 < len {
35884                // Check for non-padded variants (%-X)
35885                if bytes[i + 1] == b'-' && i + 2 < len {
35886                    let replacement = match bytes[i + 2] {
35887                        b'd' => "dd",
35888                        b'm' => "mm",
35889                        _ => {
35890                            result.push('%');
35891                            i += 1;
35892                            continue;
35893                        }
35894                    };
35895                    result.push_str(replacement);
35896                    i += 3;
35897                } else {
35898                    let replacement = match bytes[i + 1] {
35899                        b'Y' => "yyyy",
35900                        b'y' => "yy",
35901                        b'm' => "mm",
35902                        b'd' => "DD",
35903                        b'H' => "hh24",
35904                        b'M' => "mi",
35905                        b'S' => "ss",
35906                        b'f' => "ff",
35907                        _ => {
35908                            result.push('%');
35909                            i += 1;
35910                            continue;
35911                        }
35912                    };
35913                    result.push_str(replacement);
35914                    i += 2;
35915                }
35916            } else {
35917                result.push(bytes[i] as char);
35918                i += 1;
35919            }
35920        }
35921        result
35922    }
35923
35924    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
35925        // STR_TO_MAP(this, pair_delim, key_value_delim)
35926        self.write_keyword("STR_TO_MAP");
35927        self.write("(");
35928        self.generate_expression(&e.this)?;
35929        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
35930        let needs_defaults = matches!(
35931            self.config.dialect,
35932            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
35933        );
35934        if let Some(pair_delim) = &e.pair_delim {
35935            self.write(", ");
35936            self.generate_expression(pair_delim)?;
35937        } else if needs_defaults {
35938            self.write(", ','");
35939        }
35940        if let Some(key_value_delim) = &e.key_value_delim {
35941            self.write(", ");
35942            self.generate_expression(key_value_delim)?;
35943        } else if needs_defaults {
35944            self.write(", ':'");
35945        }
35946        self.write(")");
35947        Ok(())
35948    }
35949
35950    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
35951        // Detect format style: strftime (starts with %) vs Snowflake/Java
35952        let is_strftime = e.format.contains('%');
35953        // Helper: get strftime format from whatever style is stored
35954        let to_strftime = |f: &str| -> String {
35955            if is_strftime {
35956                f.to_string()
35957            } else {
35958                Self::snowflake_format_to_strftime(f)
35959            }
35960        };
35961        // Helper: get Java format
35962        let to_java = |f: &str| -> String {
35963            if is_strftime {
35964                Self::strftime_to_java_format(f)
35965            } else {
35966                Self::snowflake_format_to_spark(f)
35967            }
35968        };
35969        // Helper: get PG format
35970        let to_pg = |f: &str| -> String {
35971            if is_strftime {
35972                Self::strftime_to_postgres_format(f)
35973            } else {
35974                Self::convert_strptime_to_postgres_format(f)
35975            }
35976        };
35977
35978        match self.config.dialect {
35979            Some(DialectType::Exasol) => {
35980                self.write_keyword("TO_DATE");
35981                self.write("(");
35982                self.generate_expression(&e.this)?;
35983                self.write(", '");
35984                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
35985                self.write("'");
35986                self.write(")");
35987            }
35988            Some(DialectType::BigQuery) => {
35989                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
35990                let fmt = to_strftime(&e.format);
35991                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
35992                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
35993                self.write_keyword("PARSE_TIMESTAMP");
35994                self.write("('");
35995                self.write(&fmt);
35996                self.write("', ");
35997                self.generate_expression(&e.this)?;
35998                self.write(")");
35999            }
36000            Some(DialectType::Hive) => {
36001                // Hive: CAST(x AS TIMESTAMP) for simple date formats
36002                // Check both the raw format and the converted format (in case it's already Java)
36003                let java_fmt = to_java(&e.format);
36004                if java_fmt == "yyyy-MM-dd HH:mm:ss"
36005                    || java_fmt == "yyyy-MM-dd"
36006                    || e.format == "yyyy-MM-dd HH:mm:ss"
36007                    || e.format == "yyyy-MM-dd"
36008                {
36009                    self.write_keyword("CAST");
36010                    self.write("(");
36011                    self.generate_expression(&e.this)?;
36012                    self.write(" ");
36013                    self.write_keyword("AS TIMESTAMP");
36014                    self.write(")");
36015                } else {
36016                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
36017                    self.write_keyword("CAST");
36018                    self.write("(");
36019                    self.write_keyword("FROM_UNIXTIME");
36020                    self.write("(");
36021                    self.write_keyword("UNIX_TIMESTAMP");
36022                    self.write("(");
36023                    self.generate_expression(&e.this)?;
36024                    self.write(", '");
36025                    self.write(&java_fmt);
36026                    self.write("')");
36027                    self.write(") ");
36028                    self.write_keyword("AS TIMESTAMP");
36029                    self.write(")");
36030                }
36031            }
36032            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36033                // Spark: TO_TIMESTAMP(value, java_format)
36034                let java_fmt = to_java(&e.format);
36035                self.write_keyword("TO_TIMESTAMP");
36036                self.write("(");
36037                self.generate_expression(&e.this)?;
36038                self.write(", '");
36039                self.write(&java_fmt);
36040                self.write("')");
36041            }
36042            Some(DialectType::MySQL) => {
36043                // MySQL: STR_TO_DATE(value, format)
36044                let mut fmt = to_strftime(&e.format);
36045                // MySQL uses %e for non-padded day, %T for %H:%M:%S
36046                fmt = fmt.replace("%-d", "%e");
36047                fmt = fmt.replace("%-m", "%c");
36048                fmt = fmt.replace("%H:%M:%S", "%T");
36049                self.write_keyword("STR_TO_DATE");
36050                self.write("(");
36051                self.generate_expression(&e.this)?;
36052                self.write(", '");
36053                self.write(&fmt);
36054                self.write("')");
36055            }
36056            Some(DialectType::Drill) => {
36057                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
36058                let java_fmt = to_java(&e.format);
36059                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
36060                let java_fmt = java_fmt.replace('T', "''T''");
36061                self.write_keyword("TO_TIMESTAMP");
36062                self.write("(");
36063                self.generate_expression(&e.this)?;
36064                self.write(", '");
36065                self.write(&java_fmt);
36066                self.write("')");
36067            }
36068            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
36069                // Presto: DATE_PARSE(value, strftime_format)
36070                let mut fmt = to_strftime(&e.format);
36071                // Presto uses %e for non-padded day, %T for %H:%M:%S
36072                fmt = fmt.replace("%-d", "%e");
36073                fmt = fmt.replace("%-m", "%c");
36074                fmt = fmt.replace("%H:%M:%S", "%T");
36075                self.write_keyword("DATE_PARSE");
36076                self.write("(");
36077                self.generate_expression(&e.this)?;
36078                self.write(", '");
36079                self.write(&fmt);
36080                self.write("')");
36081            }
36082            Some(DialectType::DuckDB) => {
36083                // DuckDB: STRPTIME(value, strftime_format)
36084                let fmt = to_strftime(&e.format);
36085                self.write_keyword("STRPTIME");
36086                self.write("(");
36087                self.generate_expression(&e.this)?;
36088                self.write(", '");
36089                self.write(&fmt);
36090                self.write("')");
36091            }
36092            Some(DialectType::PostgreSQL)
36093            | Some(DialectType::Redshift)
36094            | Some(DialectType::Materialize) => {
36095                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
36096                let pg_fmt = to_pg(&e.format);
36097                self.write_keyword("TO_TIMESTAMP");
36098                self.write("(");
36099                self.generate_expression(&e.this)?;
36100                self.write(", '");
36101                self.write(&pg_fmt);
36102                self.write("')");
36103            }
36104            Some(DialectType::Oracle) => {
36105                // Oracle: TO_TIMESTAMP(value, pg_format)
36106                let pg_fmt = to_pg(&e.format);
36107                self.write_keyword("TO_TIMESTAMP");
36108                self.write("(");
36109                self.generate_expression(&e.this)?;
36110                self.write(", '");
36111                self.write(&pg_fmt);
36112                self.write("')");
36113            }
36114            Some(DialectType::Snowflake) => {
36115                // Snowflake: TO_TIMESTAMP(value, format) - native format
36116                self.write_keyword("TO_TIMESTAMP");
36117                self.write("(");
36118                self.generate_expression(&e.this)?;
36119                self.write(", '");
36120                self.write(&e.format);
36121                self.write("')");
36122            }
36123            _ => {
36124                // Default: STR_TO_TIME(this, format)
36125                self.write_keyword("STR_TO_TIME");
36126                self.write("(");
36127                self.generate_expression(&e.this)?;
36128                self.write(", '");
36129                self.write(&e.format);
36130                self.write("'");
36131                self.write(")");
36132            }
36133        }
36134        Ok(())
36135    }
36136
36137    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
36138    fn snowflake_format_to_strftime(format: &str) -> String {
36139        let mut result = String::new();
36140        let chars: Vec<char> = format.chars().collect();
36141        let mut i = 0;
36142        while i < chars.len() {
36143            let remaining = &format[i..];
36144            if remaining.starts_with("yyyy") {
36145                result.push_str("%Y");
36146                i += 4;
36147            } else if remaining.starts_with("yy") {
36148                result.push_str("%y");
36149                i += 2;
36150            } else if remaining.starts_with("mmmm") {
36151                result.push_str("%B"); // full month name
36152                i += 4;
36153            } else if remaining.starts_with("mon") {
36154                result.push_str("%b"); // abbreviated month
36155                i += 3;
36156            } else if remaining.starts_with("mm") {
36157                result.push_str("%m");
36158                i += 2;
36159            } else if remaining.starts_with("DD") {
36160                result.push_str("%d");
36161                i += 2;
36162            } else if remaining.starts_with("dy") {
36163                result.push_str("%a"); // abbreviated day name
36164                i += 2;
36165            } else if remaining.starts_with("hh24") {
36166                result.push_str("%H");
36167                i += 4;
36168            } else if remaining.starts_with("hh12") {
36169                result.push_str("%I");
36170                i += 4;
36171            } else if remaining.starts_with("hh") {
36172                result.push_str("%H");
36173                i += 2;
36174            } else if remaining.starts_with("mi") {
36175                result.push_str("%M");
36176                i += 2;
36177            } else if remaining.starts_with("ss") {
36178                result.push_str("%S");
36179                i += 2;
36180            } else if remaining.starts_with("ff") {
36181                // Fractional seconds
36182                result.push_str("%f");
36183                i += 2;
36184                // Skip digits after ff (ff3, ff6, ff9)
36185                while i < chars.len() && chars[i].is_ascii_digit() {
36186                    i += 1;
36187                }
36188            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
36189                result.push_str("%p");
36190                i += 2;
36191            } else if remaining.starts_with("tz") {
36192                result.push_str("%Z");
36193                i += 2;
36194            } else {
36195                result.push(chars[i]);
36196                i += 1;
36197            }
36198        }
36199        result
36200    }
36201
36202    /// Convert Snowflake normalized format to Spark format (Java-style)
36203    fn snowflake_format_to_spark(format: &str) -> String {
36204        let mut result = String::new();
36205        let chars: Vec<char> = format.chars().collect();
36206        let mut i = 0;
36207        while i < chars.len() {
36208            let remaining = &format[i..];
36209            if remaining.starts_with("yyyy") {
36210                result.push_str("yyyy");
36211                i += 4;
36212            } else if remaining.starts_with("yy") {
36213                result.push_str("yy");
36214                i += 2;
36215            } else if remaining.starts_with("mmmm") {
36216                result.push_str("MMMM"); // full month name
36217                i += 4;
36218            } else if remaining.starts_with("mon") {
36219                result.push_str("MMM"); // abbreviated month
36220                i += 3;
36221            } else if remaining.starts_with("mm") {
36222                result.push_str("MM");
36223                i += 2;
36224            } else if remaining.starts_with("DD") {
36225                result.push_str("dd");
36226                i += 2;
36227            } else if remaining.starts_with("dy") {
36228                result.push_str("EEE"); // abbreviated day name
36229                i += 2;
36230            } else if remaining.starts_with("hh24") {
36231                result.push_str("HH");
36232                i += 4;
36233            } else if remaining.starts_with("hh12") {
36234                result.push_str("hh");
36235                i += 4;
36236            } else if remaining.starts_with("hh") {
36237                result.push_str("HH");
36238                i += 2;
36239            } else if remaining.starts_with("mi") {
36240                result.push_str("mm");
36241                i += 2;
36242            } else if remaining.starts_with("ss") {
36243                result.push_str("ss");
36244                i += 2;
36245            } else if remaining.starts_with("ff") {
36246                result.push_str("SSS"); // milliseconds
36247                i += 2;
36248                // Skip digits after ff
36249                while i < chars.len() && chars[i].is_ascii_digit() {
36250                    i += 1;
36251                }
36252            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
36253                result.push_str("a");
36254                i += 2;
36255            } else if remaining.starts_with("tz") {
36256                result.push_str("z");
36257                i += 2;
36258            } else {
36259                result.push(chars[i]);
36260                i += 1;
36261            }
36262        }
36263        result
36264    }
36265
36266    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
36267        match self.config.dialect {
36268            Some(DialectType::DuckDB) => {
36269                // DuckDB: EPOCH(STRPTIME(value, format))
36270                self.write_keyword("EPOCH");
36271                self.write("(");
36272                self.write_keyword("STRPTIME");
36273                self.write("(");
36274                if let Some(this) = &e.this {
36275                    self.generate_expression(this)?;
36276                }
36277                if let Some(format) = &e.format {
36278                    self.write(", '");
36279                    self.write(format);
36280                    self.write("'");
36281                }
36282                self.write("))");
36283            }
36284            Some(DialectType::Hive) => {
36285                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
36286                self.write_keyword("UNIX_TIMESTAMP");
36287                self.write("(");
36288                if let Some(this) = &e.this {
36289                    self.generate_expression(this)?;
36290                }
36291                if let Some(format) = &e.format {
36292                    let java_fmt = Self::strftime_to_java_format(format);
36293                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
36294                        self.write(", '");
36295                        self.write(&java_fmt);
36296                        self.write("'");
36297                    }
36298                }
36299                self.write(")");
36300            }
36301            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36302                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
36303                self.write_keyword("UNIX_TIMESTAMP");
36304                self.write("(");
36305                if let Some(this) = &e.this {
36306                    self.generate_expression(this)?;
36307                }
36308                if let Some(format) = &e.format {
36309                    self.write(", '");
36310                    self.write(format);
36311                    self.write("'");
36312                }
36313                self.write(")");
36314            }
36315            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36316                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
36317                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
36318                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
36319                let java_fmt = Self::strftime_to_java_format(c_fmt);
36320                self.write_keyword("TO_UNIXTIME");
36321                self.write("(");
36322                self.write_keyword("COALESCE");
36323                self.write("(");
36324                self.write_keyword("TRY");
36325                self.write("(");
36326                self.write_keyword("DATE_PARSE");
36327                self.write("(");
36328                self.write_keyword("CAST");
36329                self.write("(");
36330                if let Some(this) = &e.this {
36331                    self.generate_expression(this)?;
36332                }
36333                self.write(" ");
36334                self.write_keyword("AS VARCHAR");
36335                self.write("), '");
36336                self.write(c_fmt);
36337                self.write("')), ");
36338                self.write_keyword("PARSE_DATETIME");
36339                self.write("(");
36340                self.write_keyword("DATE_FORMAT");
36341                self.write("(");
36342                self.write_keyword("CAST");
36343                self.write("(");
36344                if let Some(this) = &e.this {
36345                    self.generate_expression(this)?;
36346                }
36347                self.write(" ");
36348                self.write_keyword("AS TIMESTAMP");
36349                self.write("), '");
36350                self.write(c_fmt);
36351                self.write("'), '");
36352                self.write(&java_fmt);
36353                self.write("')))");
36354            }
36355            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36356                // Spark: UNIX_TIMESTAMP(value, java_format)
36357                self.write_keyword("UNIX_TIMESTAMP");
36358                self.write("(");
36359                if let Some(this) = &e.this {
36360                    self.generate_expression(this)?;
36361                }
36362                if let Some(format) = &e.format {
36363                    let java_fmt = Self::strftime_to_java_format(format);
36364                    self.write(", '");
36365                    self.write(&java_fmt);
36366                    self.write("'");
36367                }
36368                self.write(")");
36369            }
36370            _ => {
36371                // Default: STR_TO_UNIX(this, format)
36372                self.write_keyword("STR_TO_UNIX");
36373                self.write("(");
36374                if let Some(this) = &e.this {
36375                    self.generate_expression(this)?;
36376                }
36377                if let Some(format) = &e.format {
36378                    self.write(", '");
36379                    self.write(format);
36380                    self.write("'");
36381                }
36382                self.write(")");
36383            }
36384        }
36385        Ok(())
36386    }
36387
36388    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
36389        // STRING_TO_ARRAY(this, delimiter, null_string)
36390        self.write_keyword("STRING_TO_ARRAY");
36391        self.write("(");
36392        self.generate_expression(&e.this)?;
36393        if let Some(expression) = &e.expression {
36394            self.write(", ");
36395            self.generate_expression(expression)?;
36396        }
36397        if let Some(null_val) = &e.null {
36398            self.write(", ");
36399            self.generate_expression(null_val)?;
36400        }
36401        self.write(")");
36402        Ok(())
36403    }
36404
36405    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
36406        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36407            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
36408            self.write_keyword("OBJECT_CONSTRUCT");
36409            self.write("(");
36410            for (i, (name, expr)) in e.fields.iter().enumerate() {
36411                if i > 0 {
36412                    self.write(", ");
36413                }
36414                if let Some(name) = name {
36415                    self.write("'");
36416                    self.write(name);
36417                    self.write("'");
36418                    self.write(", ");
36419                } else {
36420                    self.write("'_");
36421                    self.write(&i.to_string());
36422                    self.write("'");
36423                    self.write(", ");
36424                }
36425                self.generate_expression(expr)?;
36426            }
36427            self.write(")");
36428        } else if self.config.struct_curly_brace_notation {
36429            // DuckDB-style: {'key': value, ...}
36430            self.write("{");
36431            for (i, (name, expr)) in e.fields.iter().enumerate() {
36432                if i > 0 {
36433                    self.write(", ");
36434                }
36435                if let Some(name) = name {
36436                    // Quote the key as a string literal
36437                    self.write("'");
36438                    self.write(name);
36439                    self.write("'");
36440                    self.write(": ");
36441                } else {
36442                    // Unnamed field: use positional key
36443                    self.write("'_");
36444                    self.write(&i.to_string());
36445                    self.write("'");
36446                    self.write(": ");
36447                }
36448                self.generate_expression(expr)?;
36449            }
36450            self.write("}");
36451        } else {
36452            // Standard SQL struct notation
36453            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
36454            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
36455            let value_as_name = matches!(
36456                self.config.dialect,
36457                Some(DialectType::BigQuery)
36458                    | Some(DialectType::Spark)
36459                    | Some(DialectType::Databricks)
36460                    | Some(DialectType::Hive)
36461            );
36462            self.write_keyword("STRUCT");
36463            self.write("(");
36464            for (i, (name, expr)) in e.fields.iter().enumerate() {
36465                if i > 0 {
36466                    self.write(", ");
36467                }
36468                if let Some(name) = name {
36469                    if value_as_name {
36470                        // STRUCT(value AS name)
36471                        self.generate_expression(expr)?;
36472                        self.write_space();
36473                        self.write_keyword("AS");
36474                        self.write_space();
36475                        // Quote name if it contains spaces or special chars
36476                        let needs_quoting = name.contains(' ') || name.contains('-');
36477                        if needs_quoting {
36478                            if matches!(
36479                                self.config.dialect,
36480                                Some(DialectType::Spark)
36481                                    | Some(DialectType::Databricks)
36482                                    | Some(DialectType::Hive)
36483                            ) {
36484                                self.write("`");
36485                                self.write(name);
36486                                self.write("`");
36487                            } else {
36488                                self.write(name);
36489                            }
36490                        } else {
36491                            self.write(name);
36492                        }
36493                    } else {
36494                        // STRUCT(name AS value)
36495                        self.write(name);
36496                        self.write_space();
36497                        self.write_keyword("AS");
36498                        self.write_space();
36499                        self.generate_expression(expr)?;
36500                    }
36501                } else {
36502                    self.generate_expression(expr)?;
36503                }
36504            }
36505            self.write(")");
36506        }
36507        Ok(())
36508    }
36509
36510    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
36511        // STUFF(this, start, length, expression)
36512        self.write_keyword("STUFF");
36513        self.write("(");
36514        self.generate_expression(&e.this)?;
36515        if let Some(start) = &e.start {
36516            self.write(", ");
36517            self.generate_expression(start)?;
36518        }
36519        if let Some(length) = e.length {
36520            self.write(", ");
36521            self.write(&length.to_string());
36522        }
36523        self.write(", ");
36524        self.generate_expression(&e.expression)?;
36525        self.write(")");
36526        Ok(())
36527    }
36528
36529    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
36530        // SUBSTRING_INDEX(this, delimiter, count)
36531        self.write_keyword("SUBSTRING_INDEX");
36532        self.write("(");
36533        self.generate_expression(&e.this)?;
36534        if let Some(delimiter) = &e.delimiter {
36535            self.write(", ");
36536            self.generate_expression(delimiter)?;
36537        }
36538        if let Some(count) = &e.count {
36539            self.write(", ");
36540            self.generate_expression(count)?;
36541        }
36542        self.write(")");
36543        Ok(())
36544    }
36545
36546    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
36547        // SUMMARIZE [TABLE] this
36548        self.write_keyword("SUMMARIZE");
36549        if e.table.is_some() {
36550            self.write_space();
36551            self.write_keyword("TABLE");
36552        }
36553        self.write_space();
36554        self.generate_expression(&e.this)?;
36555        Ok(())
36556    }
36557
36558    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
36559        // SYSTIMESTAMP
36560        self.write_keyword("SYSTIMESTAMP");
36561        Ok(())
36562    }
36563
36564    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
36565        // alias (columns...)
36566        if let Some(this) = &e.this {
36567            self.generate_expression(this)?;
36568        }
36569        if !e.columns.is_empty() {
36570            self.write("(");
36571            for (i, col) in e.columns.iter().enumerate() {
36572                if i > 0 {
36573                    self.write(", ");
36574                }
36575                self.generate_expression(col)?;
36576            }
36577            self.write(")");
36578        }
36579        Ok(())
36580    }
36581
36582    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
36583        // TABLE(this) [AS alias]
36584        self.write_keyword("TABLE");
36585        self.write("(");
36586        self.generate_expression(&e.this)?;
36587        self.write(")");
36588        if let Some(alias) = &e.alias {
36589            self.write_space();
36590            self.write_keyword("AS");
36591            self.write_space();
36592            self.write(alias);
36593        }
36594        Ok(())
36595    }
36596
36597    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
36598        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
36599        self.write_keyword("ROWS FROM");
36600        self.write(" (");
36601        for (i, expr) in e.expressions.iter().enumerate() {
36602            if i > 0 {
36603                self.write(", ");
36604            }
36605            // Each expression is either:
36606            // - A plain function (no alias)
36607            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
36608            match expr {
36609                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
36610                    // First element is the function, second is the TableAlias
36611                    self.generate_expression(&tuple.expressions[0])?;
36612                    self.write_space();
36613                    self.write_keyword("AS");
36614                    self.write_space();
36615                    self.generate_expression(&tuple.expressions[1])?;
36616                }
36617                _ => {
36618                    self.generate_expression(expr)?;
36619                }
36620            }
36621        }
36622        self.write(")");
36623        if e.ordinality {
36624            self.write_space();
36625            self.write_keyword("WITH ORDINALITY");
36626        }
36627        if let Some(alias) = &e.alias {
36628            self.write_space();
36629            self.write_keyword("AS");
36630            self.write_space();
36631            self.generate_expression(alias)?;
36632        }
36633        Ok(())
36634    }
36635
36636    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
36637        use crate::dialects::DialectType;
36638
36639        // New wrapper pattern: expression + Sample struct
36640        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
36641            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
36642            if self.config.alias_post_tablesample {
36643                // Handle Subquery with alias and Alias wrapper
36644                if let Expression::Subquery(ref s) = **this {
36645                    if let Some(ref alias) = s.alias {
36646                        // Create a clone without alias for output
36647                        let mut subquery_no_alias = (**s).clone();
36648                        subquery_no_alias.alias = None;
36649                        subquery_no_alias.column_aliases = Vec::new();
36650                        self.generate_expression(&Expression::Subquery(Box::new(
36651                            subquery_no_alias,
36652                        )))?;
36653                        self.write_space();
36654                        self.write_keyword(self.config.tablesample_keywords);
36655                        self.generate_sample_body(sample)?;
36656                        if let Some(ref seed) = sample.seed {
36657                            self.write_space();
36658                            let use_seed = sample.use_seed_keyword
36659                                && !matches!(
36660                                    self.config.dialect,
36661                                    Some(crate::dialects::DialectType::Databricks)
36662                                        | Some(crate::dialects::DialectType::Spark)
36663                                );
36664                            if use_seed {
36665                                self.write_keyword("SEED");
36666                            } else {
36667                                self.write_keyword("REPEATABLE");
36668                            }
36669                            self.write(" (");
36670                            self.generate_expression(seed)?;
36671                            self.write(")");
36672                        }
36673                        self.write_space();
36674                        self.write_keyword("AS");
36675                        self.write_space();
36676                        self.generate_identifier(alias)?;
36677                        return Ok(());
36678                    }
36679                } else if let Expression::Alias(ref a) = **this {
36680                    // Output the base expression without alias
36681                    self.generate_expression(&a.this)?;
36682                    self.write_space();
36683                    self.write_keyword(self.config.tablesample_keywords);
36684                    self.generate_sample_body(sample)?;
36685                    if let Some(ref seed) = sample.seed {
36686                        self.write_space();
36687                        let use_seed = sample.use_seed_keyword
36688                            && !matches!(
36689                                self.config.dialect,
36690                                Some(crate::dialects::DialectType::Databricks)
36691                                    | Some(crate::dialects::DialectType::Spark)
36692                            );
36693                        if use_seed {
36694                            self.write_keyword("SEED");
36695                        } else {
36696                            self.write_keyword("REPEATABLE");
36697                        }
36698                        self.write(" (");
36699                        self.generate_expression(seed)?;
36700                        self.write(")");
36701                    }
36702                    // Output alias after TABLESAMPLE
36703                    self.write_space();
36704                    self.write_keyword("AS");
36705                    self.write_space();
36706                    self.generate_identifier(&a.alias)?;
36707                    return Ok(());
36708                }
36709            }
36710            // Default: generate wrapped expression first, then TABLESAMPLE
36711            self.generate_expression(this)?;
36712            self.write_space();
36713            self.write_keyword(self.config.tablesample_keywords);
36714            self.generate_sample_body(sample)?;
36715            // Seed for table-level sample
36716            if let Some(ref seed) = sample.seed {
36717                self.write_space();
36718                // Databricks uses REPEATABLE, not SEED
36719                let use_seed = sample.use_seed_keyword
36720                    && !matches!(
36721                        self.config.dialect,
36722                        Some(crate::dialects::DialectType::Databricks)
36723                            | Some(crate::dialects::DialectType::Spark)
36724                    );
36725                if use_seed {
36726                    self.write_keyword("SEED");
36727                } else {
36728                    self.write_keyword("REPEATABLE");
36729                }
36730                self.write(" (");
36731                self.generate_expression(seed)?;
36732                self.write(")");
36733            }
36734            return Ok(());
36735        }
36736
36737        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
36738        self.write_keyword(self.config.tablesample_keywords);
36739        if let Some(method) = &e.method {
36740            self.write_space();
36741            self.write_keyword(method);
36742        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36743            // Snowflake defaults to BERNOULLI when no method is specified
36744            self.write_space();
36745            self.write_keyword("BERNOULLI");
36746        }
36747        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
36748            self.write_space();
36749            self.write_keyword("BUCKET");
36750            self.write_space();
36751            self.generate_expression(numerator)?;
36752            self.write_space();
36753            self.write_keyword("OUT OF");
36754            self.write_space();
36755            self.generate_expression(denominator)?;
36756            if let Some(field) = &e.bucket_field {
36757                self.write_space();
36758                self.write_keyword("ON");
36759                self.write_space();
36760                self.generate_expression(field)?;
36761            }
36762        } else if !e.expressions.is_empty() {
36763            self.write(" (");
36764            for (i, expr) in e.expressions.iter().enumerate() {
36765                if i > 0 {
36766                    self.write(", ");
36767                }
36768                self.generate_expression(expr)?;
36769            }
36770            self.write(")");
36771        } else if let Some(percent) = &e.percent {
36772            self.write(" (");
36773            self.generate_expression(percent)?;
36774            self.write_space();
36775            self.write_keyword("PERCENT");
36776            self.write(")");
36777        }
36778        Ok(())
36779    }
36780
36781    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
36782        // [prefix]this[postfix]
36783        if let Some(prefix) = &e.prefix {
36784            self.generate_expression(prefix)?;
36785        }
36786        if let Some(this) = &e.this {
36787            self.generate_expression(this)?;
36788        }
36789        if let Some(postfix) = &e.postfix {
36790            self.generate_expression(postfix)?;
36791        }
36792        Ok(())
36793    }
36794
36795    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
36796        // TAG (expressions)
36797        self.write_keyword("TAG");
36798        self.write(" (");
36799        for (i, expr) in e.expressions.iter().enumerate() {
36800            if i > 0 {
36801                self.write(", ");
36802            }
36803            self.generate_expression(expr)?;
36804        }
36805        self.write(")");
36806        Ok(())
36807    }
36808
36809    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
36810        // TEMPORARY or TEMP or [this] TEMPORARY
36811        if let Some(this) = &e.this {
36812            self.generate_expression(this)?;
36813            self.write_space();
36814        }
36815        self.write_keyword("TEMPORARY");
36816        Ok(())
36817    }
36818
36819    /// Generate a Time function expression
36820    /// For most dialects: TIME('value')
36821    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
36822        // Standard: TIME(value)
36823        self.write_keyword("TIME");
36824        self.write("(");
36825        self.generate_expression(&e.this)?;
36826        self.write(")");
36827        Ok(())
36828    }
36829
36830    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
36831        // TIME_ADD(this, expression, unit)
36832        self.write_keyword("TIME_ADD");
36833        self.write("(");
36834        self.generate_expression(&e.this)?;
36835        self.write(", ");
36836        self.generate_expression(&e.expression)?;
36837        if let Some(unit) = &e.unit {
36838            self.write(", ");
36839            self.write_keyword(unit);
36840        }
36841        self.write(")");
36842        Ok(())
36843    }
36844
36845    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
36846        // TIME_DIFF(this, expression, unit)
36847        self.write_keyword("TIME_DIFF");
36848        self.write("(");
36849        self.generate_expression(&e.this)?;
36850        self.write(", ");
36851        self.generate_expression(&e.expression)?;
36852        if let Some(unit) = &e.unit {
36853            self.write(", ");
36854            self.write_keyword(unit);
36855        }
36856        self.write(")");
36857        Ok(())
36858    }
36859
36860    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
36861        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
36862        self.write_keyword("TIME_FROM_PARTS");
36863        self.write("(");
36864        let mut first = true;
36865        if let Some(hour) = &e.hour {
36866            self.generate_expression(hour)?;
36867            first = false;
36868        }
36869        if let Some(minute) = &e.min {
36870            if !first {
36871                self.write(", ");
36872            }
36873            self.generate_expression(minute)?;
36874            first = false;
36875        }
36876        if let Some(second) = &e.sec {
36877            if !first {
36878                self.write(", ");
36879            }
36880            self.generate_expression(second)?;
36881            first = false;
36882        }
36883        if let Some(ns) = &e.nano {
36884            if !first {
36885                self.write(", ");
36886            }
36887            self.generate_expression(ns)?;
36888        }
36889        self.write(")");
36890        Ok(())
36891    }
36892
36893    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
36894        // TIME_SLICE(this, expression, unit)
36895        self.write_keyword("TIME_SLICE");
36896        self.write("(");
36897        self.generate_expression(&e.this)?;
36898        self.write(", ");
36899        self.generate_expression(&e.expression)?;
36900        self.write(", ");
36901        self.write_keyword(&e.unit);
36902        self.write(")");
36903        Ok(())
36904    }
36905
36906    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
36907        // TIME_STR_TO_TIME(this)
36908        self.write_keyword("TIME_STR_TO_TIME");
36909        self.write("(");
36910        self.generate_expression(&e.this)?;
36911        self.write(")");
36912        Ok(())
36913    }
36914
36915    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
36916        // TIME_SUB(this, expression, unit)
36917        self.write_keyword("TIME_SUB");
36918        self.write("(");
36919        self.generate_expression(&e.this)?;
36920        self.write(", ");
36921        self.generate_expression(&e.expression)?;
36922        if let Some(unit) = &e.unit {
36923            self.write(", ");
36924            self.write_keyword(unit);
36925        }
36926        self.write(")");
36927        Ok(())
36928    }
36929
36930    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
36931        match self.config.dialect {
36932            Some(DialectType::Exasol) => {
36933                // Exasol uses TO_CHAR with Exasol-specific format
36934                self.write_keyword("TO_CHAR");
36935                self.write("(");
36936                self.generate_expression(&e.this)?;
36937                self.write(", '");
36938                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
36939                self.write("'");
36940                self.write(")");
36941            }
36942            Some(DialectType::PostgreSQL)
36943            | Some(DialectType::Redshift)
36944            | Some(DialectType::Materialize) => {
36945                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
36946                self.write_keyword("TO_CHAR");
36947                self.write("(");
36948                self.generate_expression(&e.this)?;
36949                self.write(", '");
36950                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36951                self.write("'");
36952                self.write(")");
36953            }
36954            Some(DialectType::Oracle) => {
36955                // Oracle uses TO_CHAR with PG-like format
36956                self.write_keyword("TO_CHAR");
36957                self.write("(");
36958                self.generate_expression(&e.this)?;
36959                self.write(", '");
36960                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36961                self.write("'");
36962                self.write(")");
36963            }
36964            Some(DialectType::Drill) => {
36965                // Drill: TO_CHAR with Java format
36966                self.write_keyword("TO_CHAR");
36967                self.write("(");
36968                self.generate_expression(&e.this)?;
36969                self.write(", '");
36970                self.write(&Self::strftime_to_java_format(&e.format));
36971                self.write("'");
36972                self.write(")");
36973            }
36974            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
36975                // TSQL: FORMAT(value, format) with .NET-style format
36976                self.write_keyword("FORMAT");
36977                self.write("(");
36978                self.generate_expression(&e.this)?;
36979                self.write(", '");
36980                self.write(&Self::strftime_to_tsql_format(&e.format));
36981                self.write("'");
36982                self.write(")");
36983            }
36984            Some(DialectType::DuckDB) => {
36985                // DuckDB: STRFTIME(value, format) - keeps C format
36986                self.write_keyword("STRFTIME");
36987                self.write("(");
36988                self.generate_expression(&e.this)?;
36989                self.write(", '");
36990                self.write(&e.format);
36991                self.write("'");
36992                self.write(")");
36993            }
36994            Some(DialectType::BigQuery) => {
36995                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
36996                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
36997                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
36998                self.write_keyword("FORMAT_DATE");
36999                self.write("('");
37000                self.write(&fmt);
37001                self.write("', ");
37002                self.generate_expression(&e.this)?;
37003                self.write(")");
37004            }
37005            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37006                // Hive/Spark: DATE_FORMAT(value, java_format)
37007                self.write_keyword("DATE_FORMAT");
37008                self.write("(");
37009                self.generate_expression(&e.this)?;
37010                self.write(", '");
37011                self.write(&Self::strftime_to_java_format(&e.format));
37012                self.write("'");
37013                self.write(")");
37014            }
37015            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
37016                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
37017                self.write_keyword("DATE_FORMAT");
37018                self.write("(");
37019                self.generate_expression(&e.this)?;
37020                self.write(", '");
37021                self.write(&e.format);
37022                self.write("'");
37023                self.write(")");
37024            }
37025            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
37026                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
37027                self.write_keyword("DATE_FORMAT");
37028                self.write("(");
37029                self.generate_expression(&e.this)?;
37030                self.write(", '");
37031                self.write(&e.format);
37032                self.write("'");
37033                self.write(")");
37034            }
37035            _ => {
37036                // Default: TIME_TO_STR(this, format)
37037                self.write_keyword("TIME_TO_STR");
37038                self.write("(");
37039                self.generate_expression(&e.this)?;
37040                self.write(", '");
37041                self.write(&e.format);
37042                self.write("'");
37043                self.write(")");
37044            }
37045        }
37046        Ok(())
37047    }
37048
37049    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
37050        match self.config.dialect {
37051            Some(DialectType::DuckDB) => {
37052                // DuckDB: EPOCH(x)
37053                self.write_keyword("EPOCH");
37054                self.write("(");
37055                self.generate_expression(&e.this)?;
37056                self.write(")");
37057            }
37058            Some(DialectType::Hive)
37059            | Some(DialectType::Spark)
37060            | Some(DialectType::Databricks)
37061            | Some(DialectType::Doris)
37062            | Some(DialectType::StarRocks)
37063            | Some(DialectType::Drill) => {
37064                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
37065                self.write_keyword("UNIX_TIMESTAMP");
37066                self.write("(");
37067                self.generate_expression(&e.this)?;
37068                self.write(")");
37069            }
37070            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37071                // Presto: TO_UNIXTIME(x)
37072                self.write_keyword("TO_UNIXTIME");
37073                self.write("(");
37074                self.generate_expression(&e.this)?;
37075                self.write(")");
37076            }
37077            _ => {
37078                // Default: TIME_TO_UNIX(x)
37079                self.write_keyword("TIME_TO_UNIX");
37080                self.write("(");
37081                self.generate_expression(&e.this)?;
37082                self.write(")");
37083            }
37084        }
37085        Ok(())
37086    }
37087
37088    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
37089        match self.config.dialect {
37090            Some(DialectType::Hive) => {
37091                // Hive: TO_DATE(x)
37092                self.write_keyword("TO_DATE");
37093                self.write("(");
37094                self.generate_expression(&e.this)?;
37095                self.write(")");
37096            }
37097            _ => {
37098                // Default: TIME_STR_TO_DATE(x)
37099                self.write_keyword("TIME_STR_TO_DATE");
37100                self.write("(");
37101                self.generate_expression(&e.this)?;
37102                self.write(")");
37103            }
37104        }
37105        Ok(())
37106    }
37107
37108    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
37109        // TIME_TRUNC(this, unit)
37110        self.write_keyword("TIME_TRUNC");
37111        self.write("(");
37112        self.generate_expression(&e.this)?;
37113        self.write(", ");
37114        self.write_keyword(&e.unit);
37115        self.write(")");
37116        Ok(())
37117    }
37118
37119    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
37120        // Just output the unit name
37121        if let Some(unit) = &e.unit {
37122            self.write_keyword(unit);
37123        }
37124        Ok(())
37125    }
37126
37127    /// Generate a Timestamp function expression
37128    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
37129    /// For other dialects: TIMESTAMP('value')
37130    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
37131        use crate::dialects::DialectType;
37132        use crate::expressions::Literal;
37133
37134        match self.config.dialect {
37135            // Exasol uses TO_TIMESTAMP for Timestamp expressions
37136            Some(DialectType::Exasol) => {
37137                self.write_keyword("TO_TIMESTAMP");
37138                self.write("(");
37139                // Extract the string value from the expression if it's a string literal
37140                if let Some(this) = &e.this {
37141                    match this.as_ref() {
37142                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
37143                            let Literal::String(s) = lit.as_ref() else {
37144                                unreachable!()
37145                            };
37146                            self.write("'");
37147                            self.write(s);
37148                            self.write("'");
37149                        }
37150                        _ => {
37151                            self.generate_expression(this)?;
37152                        }
37153                    }
37154                }
37155                self.write(")");
37156            }
37157            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
37158            _ => {
37159                self.write_keyword("TIMESTAMP");
37160                self.write("(");
37161                if let Some(this) = &e.this {
37162                    self.generate_expression(this)?;
37163                }
37164                if let Some(zone) = &e.zone {
37165                    self.write(", ");
37166                    self.generate_expression(zone)?;
37167                }
37168                self.write(")");
37169            }
37170        }
37171        Ok(())
37172    }
37173
37174    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
37175        // TIMESTAMP_ADD(this, expression, unit)
37176        self.write_keyword("TIMESTAMP_ADD");
37177        self.write("(");
37178        self.generate_expression(&e.this)?;
37179        self.write(", ");
37180        self.generate_expression(&e.expression)?;
37181        if let Some(unit) = &e.unit {
37182            self.write(", ");
37183            self.write_keyword(unit);
37184        }
37185        self.write(")");
37186        Ok(())
37187    }
37188
37189    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
37190        // TIMESTAMP_DIFF(this, expression, unit)
37191        self.write_keyword("TIMESTAMP_DIFF");
37192        self.write("(");
37193        self.generate_expression(&e.this)?;
37194        self.write(", ");
37195        self.generate_expression(&e.expression)?;
37196        if let Some(unit) = &e.unit {
37197            self.write(", ");
37198            self.write_keyword(unit);
37199        }
37200        self.write(")");
37201        Ok(())
37202    }
37203
37204    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
37205        // TIMESTAMP_FROM_PARTS(this, expression)
37206        self.write_keyword("TIMESTAMP_FROM_PARTS");
37207        self.write("(");
37208        if let Some(this) = &e.this {
37209            self.generate_expression(this)?;
37210        }
37211        if let Some(expression) = &e.expression {
37212            self.write(", ");
37213            self.generate_expression(expression)?;
37214        }
37215        if let Some(zone) = &e.zone {
37216            self.write(", ");
37217            self.generate_expression(zone)?;
37218        }
37219        if let Some(milli) = &e.milli {
37220            self.write(", ");
37221            self.generate_expression(milli)?;
37222        }
37223        self.write(")");
37224        Ok(())
37225    }
37226
37227    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
37228        // TIMESTAMP_SUB(this, INTERVAL expression unit)
37229        self.write_keyword("TIMESTAMP_SUB");
37230        self.write("(");
37231        self.generate_expression(&e.this)?;
37232        self.write(", ");
37233        self.write_keyword("INTERVAL");
37234        self.write_space();
37235        self.generate_expression(&e.expression)?;
37236        if let Some(unit) = &e.unit {
37237            self.write_space();
37238            self.write_keyword(unit);
37239        }
37240        self.write(")");
37241        Ok(())
37242    }
37243
37244    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
37245        // TIMESTAMP_TZ_FROM_PARTS(...)
37246        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
37247        self.write("(");
37248        if let Some(zone) = &e.zone {
37249            self.generate_expression(zone)?;
37250        }
37251        self.write(")");
37252        Ok(())
37253    }
37254
37255    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
37256        // TO_BINARY(this, [format])
37257        self.write_keyword("TO_BINARY");
37258        self.write("(");
37259        self.generate_expression(&e.this)?;
37260        if let Some(format) = &e.format {
37261            self.write(", '");
37262            self.write(format);
37263            self.write("'");
37264        }
37265        self.write(")");
37266        Ok(())
37267    }
37268
37269    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
37270        // TO_BOOLEAN(this)
37271        self.write_keyword("TO_BOOLEAN");
37272        self.write("(");
37273        self.generate_expression(&e.this)?;
37274        self.write(")");
37275        Ok(())
37276    }
37277
37278    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
37279        // TO_CHAR(this, [format], [nlsparam])
37280        self.write_keyword("TO_CHAR");
37281        self.write("(");
37282        self.generate_expression(&e.this)?;
37283        if let Some(format) = &e.format {
37284            self.write(", '");
37285            self.write(format);
37286            self.write("'");
37287        }
37288        if let Some(nlsparam) = &e.nlsparam {
37289            self.write(", ");
37290            self.generate_expression(nlsparam)?;
37291        }
37292        self.write(")");
37293        Ok(())
37294    }
37295
37296    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
37297        // TO_DECFLOAT(this, [format])
37298        self.write_keyword("TO_DECFLOAT");
37299        self.write("(");
37300        self.generate_expression(&e.this)?;
37301        if let Some(format) = &e.format {
37302            self.write(", '");
37303            self.write(format);
37304            self.write("'");
37305        }
37306        self.write(")");
37307        Ok(())
37308    }
37309
37310    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
37311        // TO_DOUBLE(this, [format])
37312        self.write_keyword("TO_DOUBLE");
37313        self.write("(");
37314        self.generate_expression(&e.this)?;
37315        if let Some(format) = &e.format {
37316            self.write(", '");
37317            self.write(format);
37318            self.write("'");
37319        }
37320        self.write(")");
37321        Ok(())
37322    }
37323
37324    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
37325        // TO_FILE(this, path)
37326        self.write_keyword("TO_FILE");
37327        self.write("(");
37328        self.generate_expression(&e.this)?;
37329        if let Some(path) = &e.path {
37330            self.write(", ");
37331            self.generate_expression(path)?;
37332        }
37333        self.write(")");
37334        Ok(())
37335    }
37336
37337    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
37338        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
37339        // If safe flag is set, output TRY_TO_NUMBER
37340        let is_safe = e.safe.is_some();
37341        if is_safe {
37342            self.write_keyword("TRY_TO_NUMBER");
37343        } else {
37344            self.write_keyword("TO_NUMBER");
37345        }
37346        self.write("(");
37347        self.generate_expression(&e.this)?;
37348        let precision_is_snowflake_default = e.precision.is_none()
37349            || matches!(
37350                e.precision.as_deref(),
37351                Some(Expression::Literal(lit))
37352                    if matches!(lit.as_ref(), Literal::Number(n) if n == "0")
37353            );
37354        let is_snowflake_default_precision =
37355            matches!(self.config.dialect, Some(DialectType::Snowflake))
37356                && e.nlsparam.is_none()
37357                && e.scale.is_none()
37358                && matches!(
37359                    e.format.as_deref(),
37360                    Some(Expression::Literal(lit))
37361                        if matches!(lit.as_ref(), Literal::Number(n) if n == "38")
37362                )
37363                && precision_is_snowflake_default;
37364
37365        if !is_snowflake_default_precision {
37366            if let Some(format) = &e.format {
37367                self.write(", ");
37368                self.generate_expression(format)?;
37369            }
37370            if let Some(nlsparam) = &e.nlsparam {
37371                self.write(", ");
37372                self.generate_expression(nlsparam)?;
37373            }
37374            if let Some(precision) = &e.precision {
37375                self.write(", ");
37376                self.generate_expression(precision)?;
37377            }
37378            if let Some(scale) = &e.scale {
37379                self.write(", ");
37380                self.generate_expression(scale)?;
37381            }
37382        }
37383        self.write(")");
37384        Ok(())
37385    }
37386
37387    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
37388        // TO_TABLE this
37389        self.write_keyword("TO_TABLE");
37390        self.write_space();
37391        self.generate_expression(&e.this)?;
37392        Ok(())
37393    }
37394
37395    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
37396        // Check mark to determine the format
37397        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
37398            Expression::Identifier(id) => id.name.clone(),
37399            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
37400                let Literal::String(s) = lit.as_ref() else {
37401                    unreachable!()
37402                };
37403                s.clone()
37404            }
37405            _ => String::new(),
37406        });
37407
37408        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
37409        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
37410        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
37411            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
37412        });
37413
37414        // For Presto/Trino: always use START TRANSACTION
37415        let use_start_transaction = matches!(
37416            self.config.dialect,
37417            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
37418        );
37419        // For most dialects: strip TRANSACTION keyword
37420        let strip_transaction = matches!(
37421            self.config.dialect,
37422            Some(DialectType::Snowflake)
37423                | Some(DialectType::PostgreSQL)
37424                | Some(DialectType::Redshift)
37425                | Some(DialectType::MySQL)
37426                | Some(DialectType::Hive)
37427                | Some(DialectType::Spark)
37428                | Some(DialectType::Databricks)
37429                | Some(DialectType::DuckDB)
37430                | Some(DialectType::Oracle)
37431                | Some(DialectType::Doris)
37432                | Some(DialectType::StarRocks)
37433                | Some(DialectType::Materialize)
37434                | Some(DialectType::ClickHouse)
37435        );
37436
37437        if is_start || use_start_transaction {
37438            // START TRANSACTION [modes]
37439            self.write_keyword("START TRANSACTION");
37440            if let Some(modes) = &e.modes {
37441                self.write_space();
37442                self.generate_expression(modes)?;
37443            }
37444        } else {
37445            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
37446            self.write_keyword("BEGIN");
37447
37448            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
37449            let is_kind = e.this.as_ref().map_or(false, |t| {
37450                if let Expression::Identifier(id) = t.as_ref() {
37451                    id.name.eq_ignore_ascii_case("DEFERRED")
37452                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
37453                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
37454                } else {
37455                    false
37456                }
37457            });
37458
37459            // Output kind before TRANSACTION keyword
37460            if is_kind {
37461                if let Some(this) = &e.this {
37462                    self.write_space();
37463                    if let Expression::Identifier(id) = this.as_ref() {
37464                        self.write_keyword(&id.name);
37465                    }
37466                }
37467            }
37468
37469            // Output TRANSACTION keyword if it was present and target supports it
37470            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
37471                self.write_space();
37472                self.write_keyword("TRANSACTION");
37473            }
37474
37475            // Output transaction name (not kind)
37476            if !is_kind {
37477                if let Some(this) = &e.this {
37478                    self.write_space();
37479                    self.generate_expression(this)?;
37480                }
37481            }
37482
37483            // Output WITH MARK 'description' for TSQL
37484            if has_with_mark {
37485                self.write_space();
37486                self.write_keyword("WITH MARK");
37487                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
37488                    if let Literal::String(desc) = lit.as_ref() {
37489                        if !desc.is_empty() {
37490                            self.write_space();
37491                            self.write(&format!("'{}'", desc));
37492                        }
37493                    }
37494                }
37495            }
37496
37497            // Output modes (isolation levels, etc.)
37498            if let Some(modes) = &e.modes {
37499                self.write_space();
37500                self.generate_expression(modes)?;
37501            }
37502        }
37503        Ok(())
37504    }
37505
37506    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
37507        // TRANSFORM(this, expression)
37508        self.write_keyword("TRANSFORM");
37509        self.write("(");
37510        self.generate_expression(&e.this)?;
37511        self.write(", ");
37512        self.generate_expression(&e.expression)?;
37513        self.write(")");
37514        Ok(())
37515    }
37516
37517    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
37518        // TRANSFORM(expressions)
37519        self.write_keyword("TRANSFORM");
37520        self.write("(");
37521        if self.config.pretty && !e.expressions.is_empty() {
37522            self.indent_level += 1;
37523            for (i, expr) in e.expressions.iter().enumerate() {
37524                if i > 0 {
37525                    self.write(",");
37526                }
37527                self.write_newline();
37528                self.write_indent();
37529                self.generate_expression(expr)?;
37530            }
37531            self.indent_level -= 1;
37532            self.write_newline();
37533            self.write(")");
37534        } else {
37535            for (i, expr) in e.expressions.iter().enumerate() {
37536                if i > 0 {
37537                    self.write(", ");
37538                }
37539                self.generate_expression(expr)?;
37540            }
37541            self.write(")");
37542        }
37543        Ok(())
37544    }
37545
37546    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
37547        use crate::dialects::DialectType;
37548        // TRANSIENT is Snowflake-specific; skip for other dialects
37549        if let Some(this) = &e.this {
37550            self.generate_expression(this)?;
37551            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
37552                self.write_space();
37553            }
37554        }
37555        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
37556            self.write_keyword("TRANSIENT");
37557        }
37558        Ok(())
37559    }
37560
37561    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
37562        // TRANSLATE(this, from_, to)
37563        self.write_keyword("TRANSLATE");
37564        self.write("(");
37565        self.generate_expression(&e.this)?;
37566        if let Some(from) = &e.from_ {
37567            self.write(", ");
37568            self.generate_expression(from)?;
37569        }
37570        if let Some(to) = &e.to {
37571            self.write(", ");
37572            self.generate_expression(to)?;
37573        }
37574        self.write(")");
37575        Ok(())
37576    }
37577
37578    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
37579        // TRANSLATE(this USING expression)
37580        self.write_keyword("TRANSLATE");
37581        self.write("(");
37582        self.generate_expression(&e.this)?;
37583        self.write_space();
37584        self.write_keyword("USING");
37585        self.write_space();
37586        self.generate_expression(&e.expression)?;
37587        if e.with_error.is_some() {
37588            self.write_space();
37589            self.write_keyword("WITH ERROR");
37590        }
37591        self.write(")");
37592        Ok(())
37593    }
37594
37595    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
37596        // TRUNCATE TABLE table1, table2, ...
37597        self.write_keyword("TRUNCATE TABLE");
37598        self.write_space();
37599        for (i, expr) in e.expressions.iter().enumerate() {
37600            if i > 0 {
37601                self.write(", ");
37602            }
37603            self.generate_expression(expr)?;
37604        }
37605        Ok(())
37606    }
37607
37608    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
37609        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
37610        self.write_keyword("TRY_BASE64_DECODE_BINARY");
37611        self.write("(");
37612        self.generate_expression(&e.this)?;
37613        if let Some(alphabet) = &e.alphabet {
37614            self.write(", ");
37615            self.generate_expression(alphabet)?;
37616        }
37617        self.write(")");
37618        Ok(())
37619    }
37620
37621    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
37622        // TRY_BASE64_DECODE_STRING(this, [alphabet])
37623        self.write_keyword("TRY_BASE64_DECODE_STRING");
37624        self.write("(");
37625        self.generate_expression(&e.this)?;
37626        if let Some(alphabet) = &e.alphabet {
37627            self.write(", ");
37628            self.generate_expression(alphabet)?;
37629        }
37630        self.write(")");
37631        Ok(())
37632    }
37633
37634    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
37635        // TRY_TO_DECFLOAT(this, [format])
37636        self.write_keyword("TRY_TO_DECFLOAT");
37637        self.write("(");
37638        self.generate_expression(&e.this)?;
37639        if let Some(format) = &e.format {
37640            self.write(", '");
37641            self.write(format);
37642            self.write("'");
37643        }
37644        self.write(")");
37645        Ok(())
37646    }
37647
37648    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
37649        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
37650        self.write_keyword("TS_OR_DS_ADD");
37651        self.write("(");
37652        self.generate_expression(&e.this)?;
37653        self.write(", ");
37654        self.generate_expression(&e.expression)?;
37655        if let Some(unit) = &e.unit {
37656            self.write(", ");
37657            self.write_keyword(unit);
37658        }
37659        if let Some(return_type) = &e.return_type {
37660            self.write(", ");
37661            self.generate_expression(return_type)?;
37662        }
37663        self.write(")");
37664        Ok(())
37665    }
37666
37667    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
37668        // TS_OR_DS_DIFF(this, expression, [unit])
37669        self.write_keyword("TS_OR_DS_DIFF");
37670        self.write("(");
37671        self.generate_expression(&e.this)?;
37672        self.write(", ");
37673        self.generate_expression(&e.expression)?;
37674        if let Some(unit) = &e.unit {
37675            self.write(", ");
37676            self.write_keyword(unit);
37677        }
37678        self.write(")");
37679        Ok(())
37680    }
37681
37682    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
37683        let default_time_format = "%Y-%m-%d %H:%M:%S";
37684        let default_date_format = "%Y-%m-%d";
37685        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
37686            f != default_time_format && f != default_date_format
37687        });
37688
37689        if has_non_default_format {
37690            // With non-default format: dialect-specific handling
37691            let fmt = e.format.as_ref().unwrap();
37692            match self.config.dialect {
37693                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
37694                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
37695                    // STR_TO_DATE is the MySQL-native form of StrToTime
37696                    let str_to_time = crate::expressions::StrToTime {
37697                        this: Box::new((*e.this).clone()),
37698                        format: fmt.clone(),
37699                        zone: None,
37700                        safe: None,
37701                        target_type: None,
37702                    };
37703                    self.generate_str_to_time(&str_to_time)?;
37704                }
37705                Some(DialectType::Hive)
37706                | Some(DialectType::Spark)
37707                | Some(DialectType::Databricks) => {
37708                    // Hive/Spark: TO_DATE(x, java_fmt)
37709                    self.write_keyword("TO_DATE");
37710                    self.write("(");
37711                    self.generate_expression(&e.this)?;
37712                    self.write(", '");
37713                    self.write(&Self::strftime_to_java_format(fmt));
37714                    self.write("')");
37715                }
37716                Some(DialectType::Snowflake) => {
37717                    // Snowflake: TO_DATE(x, snowflake_fmt)
37718                    self.write_keyword("TO_DATE");
37719                    self.write("(");
37720                    self.generate_expression(&e.this)?;
37721                    self.write(", '");
37722                    self.write(&Self::strftime_to_snowflake_format(fmt));
37723                    self.write("')");
37724                }
37725                Some(DialectType::Doris) => {
37726                    // Doris: TO_DATE(x) - ignores format
37727                    self.write_keyword("TO_DATE");
37728                    self.write("(");
37729                    self.generate_expression(&e.this)?;
37730                    self.write(")");
37731                }
37732                _ => {
37733                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
37734                    self.write_keyword("CAST");
37735                    self.write("(");
37736                    let str_to_time = crate::expressions::StrToTime {
37737                        this: Box::new((*e.this).clone()),
37738                        format: fmt.clone(),
37739                        zone: None,
37740                        safe: None,
37741                        target_type: None,
37742                    };
37743                    self.generate_str_to_time(&str_to_time)?;
37744                    self.write_keyword(" AS ");
37745                    self.write_keyword("DATE");
37746                    self.write(")");
37747                }
37748            }
37749        } else {
37750            // Without format (or default format): simple date conversion
37751            match self.config.dialect {
37752                Some(DialectType::MySQL)
37753                | Some(DialectType::SQLite)
37754                | Some(DialectType::StarRocks) => {
37755                    // MySQL/SQLite/StarRocks: DATE(x)
37756                    self.write_keyword("DATE");
37757                    self.write("(");
37758                    self.generate_expression(&e.this)?;
37759                    self.write(")");
37760                }
37761                Some(DialectType::Hive)
37762                | Some(DialectType::Spark)
37763                | Some(DialectType::Databricks)
37764                | Some(DialectType::Snowflake)
37765                | Some(DialectType::Doris) => {
37766                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
37767                    self.write_keyword("TO_DATE");
37768                    self.write("(");
37769                    self.generate_expression(&e.this)?;
37770                    self.write(")");
37771                }
37772                Some(DialectType::Presto)
37773                | Some(DialectType::Trino)
37774                | Some(DialectType::Athena) => {
37775                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
37776                    self.write_keyword("CAST");
37777                    self.write("(");
37778                    self.write_keyword("CAST");
37779                    self.write("(");
37780                    self.generate_expression(&e.this)?;
37781                    self.write_keyword(" AS ");
37782                    self.write_keyword("TIMESTAMP");
37783                    self.write(")");
37784                    self.write_keyword(" AS ");
37785                    self.write_keyword("DATE");
37786                    self.write(")");
37787                }
37788                Some(DialectType::ClickHouse) => {
37789                    // ClickHouse: CAST(x AS Nullable(DATE))
37790                    self.write_keyword("CAST");
37791                    self.write("(");
37792                    self.generate_expression(&e.this)?;
37793                    self.write_keyword(" AS ");
37794                    self.write("Nullable(DATE)");
37795                    self.write(")");
37796                }
37797                _ => {
37798                    // Default: CAST(x AS DATE)
37799                    self.write_keyword("CAST");
37800                    self.write("(");
37801                    self.generate_expression(&e.this)?;
37802                    self.write_keyword(" AS ");
37803                    self.write_keyword("DATE");
37804                    self.write(")");
37805                }
37806            }
37807        }
37808        Ok(())
37809    }
37810
37811    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
37812        // TS_OR_DS_TO_TIME(this, [format])
37813        self.write_keyword("TS_OR_DS_TO_TIME");
37814        self.write("(");
37815        self.generate_expression(&e.this)?;
37816        if let Some(format) = &e.format {
37817            self.write(", '");
37818            self.write(format);
37819            self.write("'");
37820        }
37821        self.write(")");
37822        Ok(())
37823    }
37824
37825    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
37826        // UNHEX(this, [expression])
37827        self.write_keyword("UNHEX");
37828        self.write("(");
37829        self.generate_expression(&e.this)?;
37830        if let Some(expression) = &e.expression {
37831            self.write(", ");
37832            self.generate_expression(expression)?;
37833        }
37834        self.write(")");
37835        Ok(())
37836    }
37837
37838    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
37839        // U&this [UESCAPE escape]
37840        self.write("U&");
37841        self.generate_expression(&e.this)?;
37842        if let Some(escape) = &e.escape {
37843            self.write_space();
37844            self.write_keyword("UESCAPE");
37845            self.write_space();
37846            self.generate_expression(escape)?;
37847        }
37848        Ok(())
37849    }
37850
37851    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
37852        // UNIFORM(this, expression, [gen], [seed])
37853        self.write_keyword("UNIFORM");
37854        self.write("(");
37855        self.generate_expression(&e.this)?;
37856        self.write(", ");
37857        self.generate_expression(&e.expression)?;
37858        if let Some(gen) = &e.gen {
37859            self.write(", ");
37860            self.generate_expression(gen)?;
37861        }
37862        if let Some(seed) = &e.seed {
37863            self.write(", ");
37864            self.generate_expression(seed)?;
37865        }
37866        self.write(")");
37867        Ok(())
37868    }
37869
37870    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
37871        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
37872        self.write_keyword("UNIQUE");
37873        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
37874        if e.nulls.is_some() {
37875            self.write(" NULLS NOT DISTINCT");
37876        }
37877        if let Some(this) = &e.this {
37878            self.write_space();
37879            self.generate_expression(this)?;
37880        }
37881        if let Some(index_type) = &e.index_type {
37882            self.write(" USING ");
37883            self.generate_expression(index_type)?;
37884        }
37885        if let Some(on_conflict) = &e.on_conflict {
37886            self.write_space();
37887            self.generate_expression(on_conflict)?;
37888        }
37889        for opt in &e.options {
37890            self.write_space();
37891            self.generate_expression(opt)?;
37892        }
37893        Ok(())
37894    }
37895
37896    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
37897        // UNIQUE KEY (expressions)
37898        self.write_keyword("UNIQUE KEY");
37899        self.write(" (");
37900        for (i, expr) in e.expressions.iter().enumerate() {
37901            if i > 0 {
37902                self.write(", ");
37903            }
37904            self.generate_expression(expr)?;
37905        }
37906        self.write(")");
37907        Ok(())
37908    }
37909
37910    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
37911        // ROLLUP (r1(col1, col2), r2(col1))
37912        self.write_keyword("ROLLUP");
37913        self.write(" (");
37914        for (i, index) in e.expressions.iter().enumerate() {
37915            if i > 0 {
37916                self.write(", ");
37917            }
37918            self.generate_identifier(&index.name)?;
37919            self.write("(");
37920            for (j, col) in index.expressions.iter().enumerate() {
37921                if j > 0 {
37922                    self.write(", ");
37923                }
37924                self.generate_identifier(col)?;
37925            }
37926            self.write(")");
37927        }
37928        self.write(")");
37929        Ok(())
37930    }
37931
37932    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
37933        match self.config.dialect {
37934            Some(DialectType::DuckDB) => {
37935                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
37936                self.write_keyword("STRFTIME");
37937                self.write("(");
37938                self.write_keyword("TO_TIMESTAMP");
37939                self.write("(");
37940                self.generate_expression(&e.this)?;
37941                self.write("), '");
37942                if let Some(format) = &e.format {
37943                    self.write(format);
37944                }
37945                self.write("')");
37946            }
37947            Some(DialectType::Hive) => {
37948                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
37949                self.write_keyword("FROM_UNIXTIME");
37950                self.write("(");
37951                self.generate_expression(&e.this)?;
37952                if let Some(format) = &e.format {
37953                    if format != "yyyy-MM-dd HH:mm:ss" {
37954                        self.write(", '");
37955                        self.write(format);
37956                        self.write("'");
37957                    }
37958                }
37959                self.write(")");
37960            }
37961            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37962                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
37963                self.write_keyword("DATE_FORMAT");
37964                self.write("(");
37965                self.write_keyword("FROM_UNIXTIME");
37966                self.write("(");
37967                self.generate_expression(&e.this)?;
37968                self.write("), '");
37969                if let Some(format) = &e.format {
37970                    self.write(format);
37971                }
37972                self.write("')");
37973            }
37974            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37975                // Spark: FROM_UNIXTIME(value, format)
37976                self.write_keyword("FROM_UNIXTIME");
37977                self.write("(");
37978                self.generate_expression(&e.this)?;
37979                if let Some(format) = &e.format {
37980                    self.write(", '");
37981                    self.write(format);
37982                    self.write("'");
37983                }
37984                self.write(")");
37985            }
37986            _ => {
37987                // Default: UNIX_TO_STR(this, [format])
37988                self.write_keyword("UNIX_TO_STR");
37989                self.write("(");
37990                self.generate_expression(&e.this)?;
37991                if let Some(format) = &e.format {
37992                    self.write(", '");
37993                    self.write(format);
37994                    self.write("'");
37995                }
37996                self.write(")");
37997            }
37998        }
37999        Ok(())
38000    }
38001
38002    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
38003        use crate::dialects::DialectType;
38004        let scale = e.scale.unwrap_or(0); // 0 = seconds
38005
38006        match self.config.dialect {
38007            Some(DialectType::Snowflake) => {
38008                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
38009                self.write_keyword("TO_TIMESTAMP");
38010                self.write("(");
38011                self.generate_expression(&e.this)?;
38012                if let Some(s) = e.scale {
38013                    if s > 0 {
38014                        self.write(", ");
38015                        self.write(&s.to_string());
38016                    }
38017                }
38018                self.write(")");
38019            }
38020            Some(DialectType::BigQuery) => {
38021                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
38022                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
38023                match scale {
38024                    0 => {
38025                        self.write_keyword("TIMESTAMP_SECONDS");
38026                        self.write("(");
38027                        self.generate_expression(&e.this)?;
38028                        self.write(")");
38029                    }
38030                    3 => {
38031                        self.write_keyword("TIMESTAMP_MILLIS");
38032                        self.write("(");
38033                        self.generate_expression(&e.this)?;
38034                        self.write(")");
38035                    }
38036                    6 => {
38037                        self.write_keyword("TIMESTAMP_MICROS");
38038                        self.write("(");
38039                        self.generate_expression(&e.this)?;
38040                        self.write(")");
38041                    }
38042                    _ => {
38043                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
38044                        self.write_keyword("TIMESTAMP_SECONDS");
38045                        self.write("(CAST(");
38046                        self.generate_expression(&e.this)?;
38047                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
38048                    }
38049                }
38050            }
38051            Some(DialectType::Spark) => {
38052                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
38053                // TIMESTAMP_MILLIS(value) for scale=3
38054                // TIMESTAMP_MICROS(value) for scale=6
38055                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
38056                match scale {
38057                    0 => {
38058                        self.write_keyword("CAST");
38059                        self.write("(");
38060                        self.write_keyword("FROM_UNIXTIME");
38061                        self.write("(");
38062                        self.generate_expression(&e.this)?;
38063                        self.write(") ");
38064                        self.write_keyword("AS TIMESTAMP");
38065                        self.write(")");
38066                    }
38067                    3 => {
38068                        self.write_keyword("TIMESTAMP_MILLIS");
38069                        self.write("(");
38070                        self.generate_expression(&e.this)?;
38071                        self.write(")");
38072                    }
38073                    6 => {
38074                        self.write_keyword("TIMESTAMP_MICROS");
38075                        self.write("(");
38076                        self.generate_expression(&e.this)?;
38077                        self.write(")");
38078                    }
38079                    _ => {
38080                        self.write_keyword("TIMESTAMP_SECONDS");
38081                        self.write("(");
38082                        self.generate_expression(&e.this)?;
38083                        self.write(&format!(" / POWER(10, {}))", scale));
38084                    }
38085                }
38086            }
38087            Some(DialectType::Databricks) => {
38088                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
38089                // TIMESTAMP_MILLIS(value) for scale=3
38090                // TIMESTAMP_MICROS(value) for scale=6
38091                match scale {
38092                    0 => {
38093                        self.write_keyword("CAST");
38094                        self.write("(");
38095                        self.write_keyword("FROM_UNIXTIME");
38096                        self.write("(");
38097                        self.generate_expression(&e.this)?;
38098                        self.write(") ");
38099                        self.write_keyword("AS TIMESTAMP");
38100                        self.write(")");
38101                    }
38102                    3 => {
38103                        self.write_keyword("TIMESTAMP_MILLIS");
38104                        self.write("(");
38105                        self.generate_expression(&e.this)?;
38106                        self.write(")");
38107                    }
38108                    6 => {
38109                        self.write_keyword("TIMESTAMP_MICROS");
38110                        self.write("(");
38111                        self.generate_expression(&e.this)?;
38112                        self.write(")");
38113                    }
38114                    _ => {
38115                        self.write_keyword("TIMESTAMP_SECONDS");
38116                        self.write("(");
38117                        self.generate_expression(&e.this)?;
38118                        self.write(&format!(" / POWER(10, {}))", scale));
38119                    }
38120                }
38121            }
38122            Some(DialectType::Hive) => {
38123                // Hive: FROM_UNIXTIME(value)
38124                if scale == 0 {
38125                    self.write_keyword("FROM_UNIXTIME");
38126                    self.write("(");
38127                    self.generate_expression(&e.this)?;
38128                    self.write(")");
38129                } else {
38130                    self.write_keyword("FROM_UNIXTIME");
38131                    self.write("(");
38132                    self.generate_expression(&e.this)?;
38133                    self.write(&format!(" / POWER(10, {})", scale));
38134                    self.write(")");
38135                }
38136            }
38137            Some(DialectType::Presto) | Some(DialectType::Trino) => {
38138                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
38139                // FROM_UNIXTIME(value) for scale=0
38140                if scale == 0 {
38141                    self.write_keyword("FROM_UNIXTIME");
38142                    self.write("(");
38143                    self.generate_expression(&e.this)?;
38144                    self.write(")");
38145                } else {
38146                    self.write_keyword("FROM_UNIXTIME");
38147                    self.write("(CAST(");
38148                    self.generate_expression(&e.this)?;
38149                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
38150                }
38151            }
38152            Some(DialectType::DuckDB) => {
38153                // DuckDB: TO_TIMESTAMP(value) for scale=0
38154                // EPOCH_MS(value) for scale=3
38155                // MAKE_TIMESTAMP(value) for scale=6
38156                match scale {
38157                    0 => {
38158                        self.write_keyword("TO_TIMESTAMP");
38159                        self.write("(");
38160                        self.generate_expression(&e.this)?;
38161                        self.write(")");
38162                    }
38163                    3 => {
38164                        self.write_keyword("EPOCH_MS");
38165                        self.write("(");
38166                        self.generate_expression(&e.this)?;
38167                        self.write(")");
38168                    }
38169                    6 => {
38170                        self.write_keyword("MAKE_TIMESTAMP");
38171                        self.write("(");
38172                        self.generate_expression(&e.this)?;
38173                        self.write(")");
38174                    }
38175                    _ => {
38176                        self.write_keyword("TO_TIMESTAMP");
38177                        self.write("(");
38178                        self.generate_expression(&e.this)?;
38179                        self.write(&format!(" / POWER(10, {}))", scale));
38180                        self.write_keyword(" AT TIME ZONE");
38181                        self.write(" 'UTC'");
38182                    }
38183                }
38184            }
38185            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
38186                // Doris/StarRocks: FROM_UNIXTIME(value)
38187                self.write_keyword("FROM_UNIXTIME");
38188                self.write("(");
38189                self.generate_expression(&e.this)?;
38190                self.write(")");
38191            }
38192            Some(DialectType::Oracle) => {
38193                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
38194                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
38195                self.generate_expression(&e.this)?;
38196                self.write(" / 86400)");
38197            }
38198            Some(DialectType::Redshift) => {
38199                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
38200                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
38201                self.write("(TIMESTAMP 'epoch' + ");
38202                if scale == 0 {
38203                    self.generate_expression(&e.this)?;
38204                } else {
38205                    self.write("(");
38206                    self.generate_expression(&e.this)?;
38207                    self.write(&format!(" / POWER(10, {}))", scale));
38208                }
38209                self.write(" * INTERVAL '1 SECOND')");
38210            }
38211            Some(DialectType::Exasol) => {
38212                // Exasol: FROM_POSIX_TIME(value)
38213                self.write_keyword("FROM_POSIX_TIME");
38214                self.write("(");
38215                self.generate_expression(&e.this)?;
38216                self.write(")");
38217            }
38218            _ => {
38219                // Default: TO_TIMESTAMP(value[, scale])
38220                self.write_keyword("TO_TIMESTAMP");
38221                self.write("(");
38222                self.generate_expression(&e.this)?;
38223                if let Some(s) = e.scale {
38224                    self.write(", ");
38225                    self.write(&s.to_string());
38226                }
38227                self.write(")");
38228            }
38229        }
38230        Ok(())
38231    }
38232
38233    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
38234        // NAME col VALUE col1, col2, ...
38235        if !matches!(&*e.this, Expression::Null(_)) {
38236            self.write_keyword("NAME");
38237            self.write_space();
38238            self.generate_expression(&e.this)?;
38239        }
38240        if !e.expressions.is_empty() {
38241            self.write_space();
38242            self.write_keyword("VALUE");
38243            self.write_space();
38244            for (i, expr) in e.expressions.iter().enumerate() {
38245                if i > 0 {
38246                    self.write(", ");
38247                }
38248                self.generate_expression(expr)?;
38249            }
38250        }
38251        Ok(())
38252    }
38253
38254    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
38255        // this(expressions) or (this)(expressions)
38256        if e.wrapped.is_some() {
38257            self.write("(");
38258        }
38259        self.generate_expression(&e.this)?;
38260        if e.wrapped.is_some() {
38261            self.write(")");
38262        }
38263        self.write("(");
38264        for (i, expr) in e.expressions.iter().enumerate() {
38265            if i > 0 {
38266                self.write(", ");
38267            }
38268            self.generate_expression(expr)?;
38269        }
38270        self.write(")");
38271        Ok(())
38272    }
38273
38274    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
38275        // USING TEMPLATE this
38276        self.write_keyword("USING TEMPLATE");
38277        self.write_space();
38278        self.generate_expression(&e.this)?;
38279        Ok(())
38280    }
38281
38282    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
38283        // UTC_TIME
38284        self.write_keyword("UTC_TIME");
38285        Ok(())
38286    }
38287
38288    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
38289        if matches!(
38290            self.config.dialect,
38291            Some(crate::dialects::DialectType::ClickHouse)
38292        ) {
38293            self.write_keyword("CURRENT_TIMESTAMP");
38294            self.write("('UTC')");
38295        } else {
38296            self.write_keyword("UTC_TIMESTAMP");
38297        }
38298        Ok(())
38299    }
38300
38301    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
38302        use crate::dialects::DialectType;
38303        // Choose UUID function name based on target dialect
38304        let func_name = match self.config.dialect {
38305            Some(DialectType::Snowflake) => "UUID_STRING",
38306            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
38307            Some(DialectType::BigQuery) => "GENERATE_UUID",
38308            _ => {
38309                if let Some(name) = &e.name {
38310                    name.as_str()
38311                } else {
38312                    "UUID"
38313                }
38314            }
38315        };
38316        self.write_keyword(func_name);
38317        self.write("(");
38318        if let Some(this) = &e.this {
38319            self.generate_expression(this)?;
38320        }
38321        self.write(")");
38322        Ok(())
38323    }
38324
38325    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
38326        // MAP(key1, value1, key2, value2, ...)
38327        self.write_keyword("MAP");
38328        self.write("(");
38329        let mut first = true;
38330        for (k, v) in e.keys.iter().zip(e.values.iter()) {
38331            if !first {
38332                self.write(", ");
38333            }
38334            self.generate_expression(k)?;
38335            self.write(", ");
38336            self.generate_expression(v)?;
38337            first = false;
38338        }
38339        self.write(")");
38340        Ok(())
38341    }
38342
38343    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
38344        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
38345        self.write_keyword("VECTOR_SEARCH");
38346        self.write("(");
38347        self.generate_expression(&e.this)?;
38348        if let Some(col) = &e.column_to_search {
38349            self.write(", ");
38350            self.generate_expression(col)?;
38351        }
38352        if let Some(query_table) = &e.query_table {
38353            self.write(", ");
38354            self.generate_expression(query_table)?;
38355        }
38356        if let Some(query_col) = &e.query_column_to_search {
38357            self.write(", ");
38358            self.generate_expression(query_col)?;
38359        }
38360        if let Some(top_k) = &e.top_k {
38361            self.write(", ");
38362            self.generate_expression(top_k)?;
38363        }
38364        if let Some(dist_type) = &e.distance_type {
38365            self.write(", ");
38366            self.generate_expression(dist_type)?;
38367        }
38368        self.write(")");
38369        Ok(())
38370    }
38371
38372    fn generate_version(&mut self, e: &Version) -> Result<()> {
38373        // Python: f"FOR {expression.name} {kind} {expr}"
38374        // e.this = Identifier("TIMESTAMP" or "VERSION")
38375        // e.kind = "AS OF" (or "BETWEEN", etc.)
38376        // e.expression = the value expression
38377        // Hive does NOT use the FOR prefix for time travel
38378        use crate::dialects::DialectType;
38379        let skip_for = matches!(
38380            self.config.dialect,
38381            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
38382        );
38383        if !skip_for {
38384            self.write_keyword("FOR");
38385            self.write_space();
38386        }
38387        // Extract the name from this (which is an Identifier expression)
38388        match e.this.as_ref() {
38389            Expression::Identifier(ident) => {
38390                self.write_keyword(&ident.name);
38391            }
38392            _ => {
38393                self.generate_expression(&e.this)?;
38394            }
38395        }
38396        self.write_space();
38397        self.write_keyword(&e.kind);
38398        if let Some(expression) = &e.expression {
38399            self.write_space();
38400            self.generate_expression(expression)?;
38401        }
38402        Ok(())
38403    }
38404
38405    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
38406        // Python: return self.sql(expression, "this")
38407        self.generate_expression(&e.this)?;
38408        Ok(())
38409    }
38410
38411    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
38412        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
38413        if e.this.is_some() {
38414            self.write_keyword("NOT VOLATILE");
38415        } else {
38416            self.write_keyword("VOLATILE");
38417        }
38418        Ok(())
38419    }
38420
38421    fn generate_watermark_column_constraint(
38422        &mut self,
38423        e: &WatermarkColumnConstraint,
38424    ) -> Result<()> {
38425        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
38426        self.write_keyword("WATERMARK FOR");
38427        self.write_space();
38428        self.generate_expression(&e.this)?;
38429        self.write_space();
38430        self.write_keyword("AS");
38431        self.write_space();
38432        self.generate_expression(&e.expression)?;
38433        Ok(())
38434    }
38435
38436    fn generate_week(&mut self, e: &Week) -> Result<()> {
38437        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
38438        self.write_keyword("WEEK");
38439        self.write("(");
38440        self.generate_expression(&e.this)?;
38441        if let Some(mode) = &e.mode {
38442            self.write(", ");
38443            self.generate_expression(mode)?;
38444        }
38445        self.write(")");
38446        Ok(())
38447    }
38448
38449    fn generate_when(&mut self, e: &When) -> Result<()> {
38450        // Python: WHEN {matched}{source}{condition} THEN {then}
38451        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
38452        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
38453        self.write_keyword("WHEN");
38454        self.write_space();
38455
38456        // Check if matched
38457        if let Some(matched) = &e.matched {
38458            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
38459            match matched.as_ref() {
38460                Expression::Boolean(b) if b.value => {
38461                    self.write_keyword("MATCHED");
38462                }
38463                _ => {
38464                    self.write_keyword("NOT MATCHED");
38465                }
38466            }
38467        } else {
38468            self.write_keyword("NOT MATCHED");
38469        }
38470
38471        // BY SOURCE / BY TARGET
38472        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
38473        // BY TARGET is the default and typically omitted in output
38474        // Only emit if the dialect supports BY SOURCE syntax
38475        if self.config.matched_by_source {
38476            if let Some(source) = &e.source {
38477                if let Expression::Boolean(b) = source.as_ref() {
38478                    if b.value {
38479                        // BY SOURCE
38480                        self.write_space();
38481                        self.write_keyword("BY SOURCE");
38482                    }
38483                    // BY TARGET (b.value == false) is omitted as it's the default
38484                } else {
38485                    // For non-boolean source, output as BY SOURCE (legacy behavior)
38486                    self.write_space();
38487                    self.write_keyword("BY SOURCE");
38488                }
38489            }
38490        }
38491
38492        // Condition
38493        if let Some(condition) = &e.condition {
38494            self.write_space();
38495            self.write_keyword("AND");
38496            self.write_space();
38497            self.generate_expression(condition)?;
38498        }
38499
38500        self.write_space();
38501        self.write_keyword("THEN");
38502        self.write_space();
38503
38504        // Generate the then expression (could be INSERT, UPDATE, DELETE)
38505        // MERGE actions are stored as Tuples with the action keyword as first element
38506        self.generate_merge_action(&e.then)?;
38507
38508        Ok(())
38509    }
38510
38511    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
38512        match action {
38513            Expression::Tuple(tuple) => {
38514                let elements = &tuple.expressions;
38515                if elements.is_empty() {
38516                    return self.generate_expression(action);
38517                }
38518                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
38519                match &elements[0] {
38520                    Expression::Var(v) if v.this == "INSERT" => {
38521                        self.write_keyword("INSERT");
38522                        // Spark: INSERT * (insert all columns)
38523                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
38524                            self.write(" *");
38525                            if let Some(Expression::Where(w)) = elements.get(2) {
38526                                self.write_space();
38527                                self.generate_where(w)?;
38528                            }
38529                        } else {
38530                            let mut values_idx = 1;
38531                            // Check if second element is column list (Tuple)
38532                            if elements.len() > 1 {
38533                                if let Expression::Tuple(cols) = &elements[1] {
38534                                    // Could be columns or values - if there's a third element, second is columns
38535                                    if elements.len() > 2 {
38536                                        // Second is columns, third is values
38537                                        self.write(" (");
38538                                        for (i, col) in cols.expressions.iter().enumerate() {
38539                                            if i > 0 {
38540                                                self.write(", ");
38541                                            }
38542                                            // Strip MERGE target qualifiers from INSERT column list
38543                                            if !self.merge_strip_qualifiers.is_empty() {
38544                                                let stripped = self.strip_merge_qualifier(col);
38545                                                self.generate_expression(&stripped)?;
38546                                            } else {
38547                                                self.generate_expression(col)?;
38548                                            }
38549                                        }
38550                                        self.write(")");
38551                                        values_idx = 2;
38552                                    } else {
38553                                        // Only two elements: INSERT + values (no explicit columns)
38554                                        values_idx = 1;
38555                                    }
38556                                }
38557                            }
38558                            let mut next_idx = values_idx;
38559                            // Generate VALUES clause
38560                            if values_idx < elements.len()
38561                                && !matches!(&elements[values_idx], Expression::Where(_))
38562                            {
38563                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
38564                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
38565                                if !is_row {
38566                                    self.write_space();
38567                                    self.write_keyword("VALUES");
38568                                }
38569                                self.write(" ");
38570                                if let Expression::Tuple(vals) = &elements[values_idx] {
38571                                    self.write("(");
38572                                    for (i, val) in vals.expressions.iter().enumerate() {
38573                                        if i > 0 {
38574                                            self.write(", ");
38575                                        }
38576                                        self.generate_expression(val)?;
38577                                    }
38578                                    self.write(")");
38579                                } else {
38580                                    self.generate_expression(&elements[values_idx])?;
38581                                }
38582                                next_idx += 1;
38583                            }
38584                            if let Some(Expression::Where(w)) = elements.get(next_idx) {
38585                                self.write_space();
38586                                self.generate_where(w)?;
38587                            }
38588                        } // close else for INSERT * check
38589                    }
38590                    Expression::Var(v) if v.this == "UPDATE" => {
38591                        self.write_keyword("UPDATE");
38592                        // Spark: UPDATE * (update all columns)
38593                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
38594                            self.write(" *");
38595                            if let Some(Expression::Where(w)) = elements.get(2) {
38596                                self.write_space();
38597                                self.generate_where(w)?;
38598                            }
38599                        } else if elements.len() > 1 {
38600                            self.write_space();
38601                            self.write_keyword("SET");
38602                            // In pretty mode, put assignments on next line with extra indent
38603                            if self.config.pretty {
38604                                self.write_newline();
38605                                self.indent_level += 1;
38606                                self.write_indent();
38607                            } else {
38608                                self.write_space();
38609                            }
38610                            if let Expression::Tuple(assignments) = &elements[1] {
38611                                for (i, assignment) in assignments.expressions.iter().enumerate() {
38612                                    if i > 0 {
38613                                        if self.config.pretty {
38614                                            self.write(",");
38615                                            self.write_newline();
38616                                            self.write_indent();
38617                                        } else {
38618                                            self.write(", ");
38619                                        }
38620                                    }
38621                                    // Strip MERGE target qualifiers from left side of UPDATE SET
38622                                    if !self.merge_strip_qualifiers.is_empty() {
38623                                        self.generate_merge_set_assignment(assignment)?;
38624                                    } else {
38625                                        self.generate_expression(assignment)?;
38626                                    }
38627                                }
38628                            } else {
38629                                self.generate_expression(&elements[1])?;
38630                            }
38631                            if self.config.pretty {
38632                                self.indent_level -= 1;
38633                            }
38634                            if let Some(Expression::Where(w)) = elements.get(2) {
38635                                self.write_space();
38636                                self.generate_where(w)?;
38637                            }
38638                        }
38639                    }
38640                    Expression::Var(v) if v.this == "DELETE" => {
38641                        self.write_keyword("DELETE");
38642                        if let Some(Expression::Where(w)) = elements.get(1) {
38643                            self.write_space();
38644                            self.generate_where(w)?;
38645                        }
38646                    }
38647                    _ => {
38648                        // Fallback: generic tuple generation
38649                        self.generate_expression(action)?;
38650                    }
38651                }
38652            }
38653            Expression::Var(v)
38654                if v.this == "INSERT"
38655                    || v.this == "UPDATE"
38656                    || v.this == "DELETE"
38657                    || v.this == "DO NOTHING" =>
38658            {
38659                self.write_keyword(&v.this);
38660            }
38661            _ => {
38662                self.generate_expression(action)?;
38663            }
38664        }
38665        Ok(())
38666    }
38667
38668    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
38669    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
38670        match assignment {
38671            Expression::Eq(eq) => {
38672                // Strip qualifier from the left side if it matches a MERGE target name
38673                let stripped_left = self.strip_merge_qualifier(&eq.left);
38674                self.generate_expression(&stripped_left)?;
38675                self.write(" = ");
38676                self.generate_expression(&eq.right)?;
38677                Ok(())
38678            }
38679            other => self.generate_expression(other),
38680        }
38681    }
38682
38683    /// Strip table qualifier from a column reference if it matches a MERGE target name
38684    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
38685        match expr {
38686            Expression::Column(col) => {
38687                if let Some(ref table_ident) = col.table {
38688                    if self
38689                        .merge_strip_qualifiers
38690                        .iter()
38691                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
38692                    {
38693                        // Strip the table qualifier
38694                        let mut col = col.clone();
38695                        col.table = None;
38696                        return Expression::Column(col);
38697                    }
38698                }
38699                expr.clone()
38700            }
38701            Expression::Dot(dot) => {
38702                // table.column -> column (strip qualifier)
38703                if let Expression::Identifier(id) = &dot.this {
38704                    if self
38705                        .merge_strip_qualifiers
38706                        .iter()
38707                        .any(|n| n.eq_ignore_ascii_case(&id.name))
38708                    {
38709                        return Expression::Identifier(dot.field.clone());
38710                    }
38711                }
38712                expr.clone()
38713            }
38714            _ => expr.clone(),
38715        }
38716    }
38717
38718    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
38719        // Python: return self.expressions(expression, sep=" ", indent=False)
38720        for (i, expr) in e.expressions.iter().enumerate() {
38721            if i > 0 {
38722                // In pretty mode, each WHEN clause on its own line
38723                if self.config.pretty {
38724                    self.write_newline();
38725                    self.write_indent();
38726                } else {
38727                    self.write_space();
38728                }
38729            }
38730            self.generate_expression(expr)?;
38731        }
38732        Ok(())
38733    }
38734
38735    fn generate_where(&mut self, e: &Where) -> Result<()> {
38736        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
38737        self.write_keyword("WHERE");
38738        self.write_space();
38739        self.generate_expression(&e.this)?;
38740        Ok(())
38741    }
38742
38743    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
38744        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
38745        self.write_keyword("WIDTH_BUCKET");
38746        self.write("(");
38747        self.generate_expression(&e.this)?;
38748        if let Some(min_value) = &e.min_value {
38749            self.write(", ");
38750            self.generate_expression(min_value)?;
38751        }
38752        if let Some(max_value) = &e.max_value {
38753            self.write(", ");
38754            self.generate_expression(max_value)?;
38755        }
38756        if let Some(num_buckets) = &e.num_buckets {
38757            self.write(", ");
38758            self.generate_expression(num_buckets)?;
38759        }
38760        self.write(")");
38761        Ok(())
38762    }
38763
38764    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
38765        // Window specification: PARTITION BY ... ORDER BY ... frame
38766        self.generate_window_spec(e)
38767    }
38768
38769    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
38770        // Window specification: PARTITION BY ... ORDER BY ... frame
38771        let mut has_content = false;
38772
38773        // PARTITION BY
38774        if !e.partition_by.is_empty() {
38775            self.write_keyword("PARTITION BY");
38776            self.write_space();
38777            for (i, expr) in e.partition_by.iter().enumerate() {
38778                if i > 0 {
38779                    self.write(", ");
38780                }
38781                self.generate_expression(expr)?;
38782            }
38783            has_content = true;
38784        }
38785
38786        // ORDER BY
38787        if !e.order_by.is_empty() {
38788            if has_content {
38789                self.write_space();
38790            }
38791            self.write_keyword("ORDER BY");
38792            self.write_space();
38793            for (i, ordered) in e.order_by.iter().enumerate() {
38794                if i > 0 {
38795                    self.write(", ");
38796                }
38797                self.generate_expression(&ordered.this)?;
38798                if ordered.desc {
38799                    self.write_space();
38800                    self.write_keyword("DESC");
38801                } else if ordered.explicit_asc {
38802                    self.write_space();
38803                    self.write_keyword("ASC");
38804                }
38805                if let Some(nulls_first) = ordered.nulls_first {
38806                    self.write_space();
38807                    self.write_keyword("NULLS");
38808                    self.write_space();
38809                    if nulls_first {
38810                        self.write_keyword("FIRST");
38811                    } else {
38812                        self.write_keyword("LAST");
38813                    }
38814                }
38815            }
38816            has_content = true;
38817        }
38818
38819        // Frame specification
38820        if let Some(frame) = &e.frame {
38821            if has_content {
38822                self.write_space();
38823            }
38824            self.generate_window_frame(frame)?;
38825        }
38826
38827        Ok(())
38828    }
38829
38830    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
38831        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
38832        self.write_keyword("WITH");
38833        self.write_space();
38834        if e.no.is_some() {
38835            self.write_keyword("NO");
38836            self.write_space();
38837        }
38838        self.write_keyword("DATA");
38839
38840        // statistics
38841        if let Some(statistics) = &e.statistics {
38842            self.write_space();
38843            self.write_keyword("AND");
38844            self.write_space();
38845            // Check if statistics is true or false
38846            match statistics.as_ref() {
38847                Expression::Boolean(b) if !b.value => {
38848                    self.write_keyword("NO");
38849                    self.write_space();
38850                }
38851                _ => {}
38852            }
38853            self.write_keyword("STATISTICS");
38854        }
38855        Ok(())
38856    }
38857
38858    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
38859        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
38860        self.write_keyword("WITH FILL");
38861
38862        if let Some(from_) = &e.from_ {
38863            self.write_space();
38864            self.write_keyword("FROM");
38865            self.write_space();
38866            self.generate_expression(from_)?;
38867        }
38868
38869        if let Some(to) = &e.to {
38870            self.write_space();
38871            self.write_keyword("TO");
38872            self.write_space();
38873            self.generate_expression(to)?;
38874        }
38875
38876        if let Some(step) = &e.step {
38877            self.write_space();
38878            self.write_keyword("STEP");
38879            self.write_space();
38880            self.generate_expression(step)?;
38881        }
38882
38883        if let Some(staleness) = &e.staleness {
38884            self.write_space();
38885            self.write_keyword("STALENESS");
38886            self.write_space();
38887            self.generate_expression(staleness)?;
38888        }
38889
38890        if let Some(interpolate) = &e.interpolate {
38891            self.write_space();
38892            self.write_keyword("INTERPOLATE");
38893            self.write(" (");
38894            // INTERPOLATE items use reversed alias format: name AS expression
38895            self.generate_interpolate_item(interpolate)?;
38896            self.write(")");
38897        }
38898
38899        Ok(())
38900    }
38901
38902    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
38903    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
38904        match expr {
38905            Expression::Alias(alias) => {
38906                // Output as: alias_name AS expression
38907                self.generate_identifier(&alias.alias)?;
38908                self.write_space();
38909                self.write_keyword("AS");
38910                self.write_space();
38911                self.generate_expression(&alias.this)?;
38912            }
38913            Expression::Tuple(tuple) => {
38914                for (i, item) in tuple.expressions.iter().enumerate() {
38915                    if i > 0 {
38916                        self.write(", ");
38917                    }
38918                    self.generate_interpolate_item(item)?;
38919                }
38920            }
38921            other => {
38922                self.generate_expression(other)?;
38923            }
38924        }
38925        Ok(())
38926    }
38927
38928    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
38929        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
38930        self.write_keyword("WITH JOURNAL TABLE");
38931        self.write("=");
38932        self.generate_expression(&e.this)?;
38933        Ok(())
38934    }
38935
38936    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
38937        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
38938        self.generate_expression(&e.this)?;
38939        self.write_space();
38940        self.write_keyword("WITH");
38941        self.write_space();
38942        self.write_keyword(&e.op);
38943        Ok(())
38944    }
38945
38946    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
38947        // Python: return f"WITH {self.expressions(expression, flat=True)}"
38948        self.write_keyword("WITH");
38949        self.write_space();
38950        for (i, expr) in e.expressions.iter().enumerate() {
38951            if i > 0 {
38952                self.write(", ");
38953            }
38954            self.generate_expression(expr)?;
38955        }
38956        Ok(())
38957    }
38958
38959    fn generate_with_schema_binding_property(
38960        &mut self,
38961        e: &WithSchemaBindingProperty,
38962    ) -> Result<()> {
38963        // Python: return f"WITH {self.sql(expression, 'this')}"
38964        self.write_keyword("WITH");
38965        self.write_space();
38966        self.generate_expression(&e.this)?;
38967        Ok(())
38968    }
38969
38970    fn generate_with_system_versioning_property(
38971        &mut self,
38972        e: &WithSystemVersioningProperty,
38973    ) -> Result<()> {
38974        // Python: complex logic for SYSTEM_VERSIONING with options
38975        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
38976        // or SYSTEM_VERSIONING=ON/OFF
38977        // with WITH(...) wrapper if with_ is set
38978
38979        let mut parts = Vec::new();
38980
38981        if let Some(this) = &e.this {
38982            // HISTORY_TABLE=...
38983            let mut s = String::from("HISTORY_TABLE=");
38984            let mut gen = Generator::new();
38985            gen.generate_expression(this)?;
38986            s.push_str(&gen.output);
38987            parts.push(s);
38988        }
38989
38990        if let Some(data_consistency) = &e.data_consistency {
38991            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
38992            let mut gen = Generator::new();
38993            gen.generate_expression(data_consistency)?;
38994            s.push_str(&gen.output);
38995            parts.push(s);
38996        }
38997
38998        if let Some(retention_period) = &e.retention_period {
38999            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
39000            let mut gen = Generator::new();
39001            gen.generate_expression(retention_period)?;
39002            s.push_str(&gen.output);
39003            parts.push(s);
39004        }
39005
39006        self.write_keyword("SYSTEM_VERSIONING");
39007        self.write("=");
39008
39009        if !parts.is_empty() {
39010            self.write_keyword("ON");
39011            self.write("(");
39012            self.write(&parts.join(", "));
39013            self.write(")");
39014        } else if e.on.is_some() {
39015            self.write_keyword("ON");
39016        } else {
39017            self.write_keyword("OFF");
39018        }
39019
39020        // Wrap in WITH(...) if with_ is set
39021        if e.with_.is_some() {
39022            let inner = self.output.clone();
39023            self.output.clear();
39024            self.write("WITH(");
39025            self.write(&inner);
39026            self.write(")");
39027        }
39028
39029        Ok(())
39030    }
39031
39032    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
39033        // Python: f"WITH ({self.expressions(expression, flat=True)})"
39034        self.write_keyword("WITH");
39035        self.write(" (");
39036        for (i, expr) in e.expressions.iter().enumerate() {
39037            if i > 0 {
39038                self.write(", ");
39039            }
39040            self.generate_expression(expr)?;
39041        }
39042        self.write(")");
39043        Ok(())
39044    }
39045
39046    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
39047        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
39048        // return self.func("XMLELEMENT", name, *expression.expressions)
39049        self.write_keyword("XMLELEMENT");
39050        self.write("(");
39051
39052        if e.evalname.is_some() {
39053            self.write_keyword("EVALNAME");
39054        } else {
39055            self.write_keyword("NAME");
39056        }
39057        self.write_space();
39058        self.generate_expression(&e.this)?;
39059
39060        for expr in &e.expressions {
39061            self.write(", ");
39062            self.generate_expression(expr)?;
39063        }
39064        self.write(")");
39065        Ok(())
39066    }
39067
39068    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
39069        // XMLGET(this, expression [, instance])
39070        self.write_keyword("XMLGET");
39071        self.write("(");
39072        self.generate_expression(&e.this)?;
39073        self.write(", ");
39074        self.generate_expression(&e.expression)?;
39075        if let Some(instance) = &e.instance {
39076            self.write(", ");
39077            self.generate_expression(instance)?;
39078        }
39079        self.write(")");
39080        Ok(())
39081    }
39082
39083    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
39084        // Python: this + optional (expr)
39085        self.generate_expression(&e.this)?;
39086        if let Some(expression) = &e.expression {
39087            self.write("(");
39088            self.generate_expression(expression)?;
39089            self.write(")");
39090        }
39091        Ok(())
39092    }
39093
39094    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
39095        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
39096        self.write_keyword("XMLTABLE");
39097        self.write("(");
39098
39099        if self.config.pretty {
39100            self.indent_level += 1;
39101            self.write_newline();
39102            self.write_indent();
39103            self.generate_expression(&e.this)?;
39104
39105            if let Some(passing) = &e.passing {
39106                self.write_newline();
39107                self.write_indent();
39108                self.write_keyword("PASSING");
39109                if let Expression::Tuple(tuple) = passing.as_ref() {
39110                    for expr in &tuple.expressions {
39111                        self.write_newline();
39112                        self.indent_level += 1;
39113                        self.write_indent();
39114                        self.generate_expression(expr)?;
39115                        self.indent_level -= 1;
39116                    }
39117                } else {
39118                    self.write_newline();
39119                    self.indent_level += 1;
39120                    self.write_indent();
39121                    self.generate_expression(passing)?;
39122                    self.indent_level -= 1;
39123                }
39124            }
39125
39126            if e.by_ref.is_some() {
39127                self.write_newline();
39128                self.write_indent();
39129                self.write_keyword("RETURNING SEQUENCE BY REF");
39130            }
39131
39132            if !e.columns.is_empty() {
39133                self.write_newline();
39134                self.write_indent();
39135                self.write_keyword("COLUMNS");
39136                for (i, col) in e.columns.iter().enumerate() {
39137                    self.write_newline();
39138                    self.indent_level += 1;
39139                    self.write_indent();
39140                    self.generate_expression(col)?;
39141                    self.indent_level -= 1;
39142                    if i < e.columns.len() - 1 {
39143                        self.write(",");
39144                    }
39145                }
39146            }
39147
39148            self.indent_level -= 1;
39149            self.write_newline();
39150            self.write_indent();
39151            self.write(")");
39152            return Ok(());
39153        }
39154
39155        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
39156        if let Some(namespaces) = &e.namespaces {
39157            self.write_keyword("XMLNAMESPACES");
39158            self.write("(");
39159            // Unwrap Tuple if present to avoid extra parentheses
39160            if let Expression::Tuple(tuple) = namespaces.as_ref() {
39161                for (i, expr) in tuple.expressions.iter().enumerate() {
39162                    if i > 0 {
39163                        self.write(", ");
39164                    }
39165                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
39166                    // See xmlnamespace_sql in generator.py
39167                    if !matches!(expr, Expression::Alias(_)) {
39168                        self.write_keyword("DEFAULT");
39169                        self.write_space();
39170                    }
39171                    self.generate_expression(expr)?;
39172                }
39173            } else {
39174                // Single namespace - check if DEFAULT
39175                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
39176                    self.write_keyword("DEFAULT");
39177                    self.write_space();
39178                }
39179                self.generate_expression(namespaces)?;
39180            }
39181            self.write("), ");
39182        }
39183
39184        // XPath expression
39185        self.generate_expression(&e.this)?;
39186
39187        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
39188        if let Some(passing) = &e.passing {
39189            self.write_space();
39190            self.write_keyword("PASSING");
39191            self.write_space();
39192            // Unwrap Tuple if present to avoid extra parentheses
39193            if let Expression::Tuple(tuple) = passing.as_ref() {
39194                for (i, expr) in tuple.expressions.iter().enumerate() {
39195                    if i > 0 {
39196                        self.write(", ");
39197                    }
39198                    self.generate_expression(expr)?;
39199                }
39200            } else {
39201                self.generate_expression(passing)?;
39202            }
39203        }
39204
39205        // RETURNING SEQUENCE BY REF
39206        if e.by_ref.is_some() {
39207            self.write_space();
39208            self.write_keyword("RETURNING SEQUENCE BY REF");
39209        }
39210
39211        // COLUMNS clause
39212        if !e.columns.is_empty() {
39213            self.write_space();
39214            self.write_keyword("COLUMNS");
39215            self.write_space();
39216            for (i, col) in e.columns.iter().enumerate() {
39217                if i > 0 {
39218                    self.write(", ");
39219                }
39220                self.generate_expression(col)?;
39221            }
39222        }
39223
39224        self.write(")");
39225        Ok(())
39226    }
39227
39228    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
39229        // Python: return self.connector_sql(expression, "XOR", stack)
39230        // Handles: this XOR expression or expressions joined by XOR
39231        if let Some(this) = &e.this {
39232            self.generate_expression(this)?;
39233            if let Some(expression) = &e.expression {
39234                self.write_space();
39235                self.write_keyword("XOR");
39236                self.write_space();
39237                self.generate_expression(expression)?;
39238            }
39239        }
39240
39241        // Handle multiple expressions
39242        for (i, expr) in e.expressions.iter().enumerate() {
39243            if i > 0 || e.this.is_some() {
39244                self.write_space();
39245                self.write_keyword("XOR");
39246                self.write_space();
39247            }
39248            self.generate_expression(expr)?;
39249        }
39250        Ok(())
39251    }
39252
39253    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
39254        // ZIPF(this, elementcount [, gen])
39255        self.write_keyword("ZIPF");
39256        self.write("(");
39257        self.generate_expression(&e.this)?;
39258        if let Some(elementcount) = &e.elementcount {
39259            self.write(", ");
39260            self.generate_expression(elementcount)?;
39261        }
39262        if let Some(gen) = &e.gen {
39263            self.write(", ");
39264            self.generate_expression(gen)?;
39265        }
39266        self.write(")");
39267        Ok(())
39268    }
39269}
39270
39271impl Default for Generator {
39272    fn default() -> Self {
39273        Self::new()
39274    }
39275}
39276
39277#[cfg(test)]
39278mod tests {
39279    use super::*;
39280    use crate::parser::Parser;
39281
39282    fn roundtrip(sql: &str) -> String {
39283        let ast = Parser::parse_sql(sql).unwrap();
39284        Generator::sql(&ast[0]).unwrap()
39285    }
39286
39287    #[test]
39288    fn test_simple_select() {
39289        let result = roundtrip("SELECT 1");
39290        assert_eq!(result, "SELECT 1");
39291    }
39292
39293    #[test]
39294    fn test_select_from() {
39295        let result = roundtrip("SELECT a, b FROM t");
39296        assert_eq!(result, "SELECT a, b FROM t");
39297    }
39298
39299    #[test]
39300    fn test_select_where() {
39301        let result = roundtrip("SELECT * FROM t WHERE x = 1");
39302        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
39303    }
39304
39305    #[test]
39306    fn test_select_join() {
39307        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
39308        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
39309    }
39310
39311    #[test]
39312    fn test_insert() {
39313        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
39314        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
39315    }
39316
39317    #[test]
39318    fn test_pretty_print() {
39319        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
39320        let result = Generator::pretty_sql(&ast[0]).unwrap();
39321        assert!(result.contains('\n'));
39322    }
39323
39324    #[test]
39325    fn test_window_function() {
39326        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
39327        assert_eq!(
39328            result,
39329            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
39330        );
39331    }
39332
39333    #[test]
39334    fn test_window_function_with_frame() {
39335        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
39336        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
39337    }
39338
39339    #[test]
39340    fn test_aggregate_with_filter() {
39341        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
39342        assert_eq!(
39343            result,
39344            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
39345        );
39346    }
39347
39348    #[test]
39349    fn test_subscript() {
39350        let result = roundtrip("SELECT arr[0]");
39351        assert_eq!(result, "SELECT arr[0]");
39352    }
39353
39354    // DDL tests
39355    #[test]
39356    fn test_create_table() {
39357        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
39358        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
39359    }
39360
39361    #[test]
39362    fn test_create_table_with_constraints() {
39363        let result = roundtrip(
39364            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
39365        );
39366        assert_eq!(
39367            result,
39368            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
39369        );
39370    }
39371
39372    #[test]
39373    fn test_create_table_if_not_exists() {
39374        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
39375        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
39376    }
39377
39378    #[test]
39379    fn test_drop_table() {
39380        let result = roundtrip("DROP TABLE users");
39381        assert_eq!(result, "DROP TABLE users");
39382    }
39383
39384    #[test]
39385    fn test_drop_table_if_exists_cascade() {
39386        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
39387        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
39388    }
39389
39390    #[test]
39391    fn test_alter_table_add_column() {
39392        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
39393        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
39394    }
39395
39396    #[test]
39397    fn test_alter_table_drop_column() {
39398        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
39399        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
39400    }
39401
39402    #[test]
39403    fn test_create_index() {
39404        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
39405        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
39406    }
39407
39408    #[test]
39409    fn test_create_unique_index() {
39410        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
39411        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
39412    }
39413
39414    #[test]
39415    fn test_drop_index() {
39416        let result = roundtrip("DROP INDEX idx_name");
39417        assert_eq!(result, "DROP INDEX idx_name");
39418
39419        let result = roundtrip(r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#);
39420        assert_eq!(
39421            result,
39422            r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#
39423        );
39424
39425        let result = roundtrip(r#"DROP INDEX "public"."IdxMixed""#);
39426        assert_eq!(result, r#"DROP INDEX "public"."IdxMixed""#);
39427    }
39428
39429    #[test]
39430    fn test_create_view() {
39431        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
39432        assert_eq!(
39433            result,
39434            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
39435        );
39436    }
39437
39438    #[test]
39439    fn test_drop_view() {
39440        let result = roundtrip("DROP VIEW active_users");
39441        assert_eq!(result, "DROP VIEW active_users");
39442    }
39443
39444    #[test]
39445    fn test_truncate() {
39446        let result = roundtrip("TRUNCATE TABLE users");
39447        assert_eq!(result, "TRUNCATE TABLE users");
39448    }
39449
39450    #[test]
39451    fn test_string_literal_escaping_default() {
39452        // Default: double single quotes
39453        let result = roundtrip("SELECT 'hello'");
39454        assert_eq!(result, "SELECT 'hello'");
39455
39456        // Single quotes are doubled
39457        let result = roundtrip("SELECT 'it''s a test'");
39458        assert_eq!(result, "SELECT 'it''s a test'");
39459    }
39460
39461    #[test]
39462    fn test_not_in_style_prefix_default_generic() {
39463        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
39464        assert_eq!(
39465            result,
39466            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
39467        );
39468    }
39469
39470    #[test]
39471    fn test_not_in_style_infix_generic_override() {
39472        let ast =
39473            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
39474                .unwrap();
39475        let config = GeneratorConfig {
39476            not_in_style: NotInStyle::Infix,
39477            ..Default::default()
39478        };
39479        let mut gen = Generator::with_config(config);
39480        let result = gen.generate(&ast[0]).unwrap();
39481        assert_eq!(
39482            result,
39483            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
39484        );
39485    }
39486
39487    #[test]
39488    fn test_string_literal_escaping_mysql() {
39489        use crate::dialects::DialectType;
39490
39491        let config = GeneratorConfig {
39492            dialect: Some(DialectType::MySQL),
39493            ..Default::default()
39494        };
39495
39496        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39497        let mut gen = Generator::with_config(config.clone());
39498        let result = gen.generate(&ast[0]).unwrap();
39499        assert_eq!(result, "SELECT 'hello'");
39500
39501        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
39502        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39503        let mut gen = Generator::with_config(config.clone());
39504        let result = gen.generate(&ast[0]).unwrap();
39505        assert_eq!(result, "SELECT 'it''s'");
39506    }
39507
39508    #[test]
39509    fn test_string_literal_escaping_postgres() {
39510        use crate::dialects::DialectType;
39511
39512        let config = GeneratorConfig {
39513            dialect: Some(DialectType::PostgreSQL),
39514            ..Default::default()
39515        };
39516
39517        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39518        let mut gen = Generator::with_config(config.clone());
39519        let result = gen.generate(&ast[0]).unwrap();
39520        assert_eq!(result, "SELECT 'hello'");
39521
39522        // PostgreSQL uses doubled quotes for regular strings
39523        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39524        let mut gen = Generator::with_config(config.clone());
39525        let result = gen.generate(&ast[0]).unwrap();
39526        assert_eq!(result, "SELECT 'it''s'");
39527    }
39528
39529    #[test]
39530    fn test_string_literal_escaping_bigquery() {
39531        use crate::dialects::DialectType;
39532
39533        let config = GeneratorConfig {
39534            dialect: Some(DialectType::BigQuery),
39535            ..Default::default()
39536        };
39537
39538        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39539        let mut gen = Generator::with_config(config.clone());
39540        let result = gen.generate(&ast[0]).unwrap();
39541        assert_eq!(result, "SELECT 'hello'");
39542
39543        // BigQuery escapes single quotes with backslash
39544        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39545        let mut gen = Generator::with_config(config.clone());
39546        let result = gen.generate(&ast[0]).unwrap();
39547        assert_eq!(result, "SELECT 'it\\'s'");
39548    }
39549
39550    #[test]
39551    fn test_generate_deep_and_chain_without_stack_growth() {
39552        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
39553            Expression::column("c0"),
39554            Expression::number(0),
39555        )));
39556
39557        for i in 1..2500 {
39558            let predicate = Expression::Eq(Box::new(BinaryOp::new(
39559                Expression::column(format!("c{i}")),
39560                Expression::number(i as i64),
39561            )));
39562            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
39563        }
39564
39565        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
39566        assert!(sql.contains("c2499 = 2499"), "{}", sql);
39567    }
39568
39569    #[test]
39570    fn test_generate_deep_or_chain_without_stack_growth() {
39571        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
39572            Expression::column("c0"),
39573            Expression::number(0),
39574        )));
39575
39576        for i in 1..2500 {
39577            let predicate = Expression::Eq(Box::new(BinaryOp::new(
39578                Expression::column(format!("c{i}")),
39579                Expression::number(i as i64),
39580            )));
39581            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
39582        }
39583
39584        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
39585        assert!(sql.contains("c2499 = 2499"), "{}", sql);
39586    }
39587}