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                if let Some(idx) = f.index {
22126                    if matches!(
22127                        self.config.dialect,
22128                        Some(DialectType::TSQL) | Some(DialectType::Fabric)
22129                    ) {
22130                        self.write("@P");
22131                        self.write(&idx.to_string());
22132                    } else {
22133                        self.write("$");
22134                        self.write(&idx.to_string());
22135                    }
22136                } else {
22137                    self.write("$");
22138                    if let Some(ref name) = f.name {
22139                        // Session variable like $x or $query_id
22140                        self.write(name);
22141                    }
22142                }
22143            }
22144            ParameterStyle::DollarBrace => {
22145                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
22146                self.write("${");
22147                if let Some(ref name) = f.name {
22148                    self.write(name);
22149                }
22150                if let Some(ref expr) = f.expression {
22151                    self.write(":");
22152                    self.write(expr);
22153                }
22154                self.write("}");
22155            }
22156            ParameterStyle::Colon => {
22157                self.write(":");
22158                if let Some(idx) = f.index {
22159                    self.write(&idx.to_string());
22160                } else if let Some(ref name) = f.name {
22161                    self.write(name);
22162                }
22163            }
22164            ParameterStyle::At => {
22165                self.write("@");
22166                if let Some(ref name) = f.name {
22167                    if f.string_quoted {
22168                        self.write("'");
22169                        self.write(name);
22170                        self.write("'");
22171                    } else if f.quoted {
22172                        self.write("\"");
22173                        self.write(name);
22174                        self.write("\"");
22175                    } else {
22176                        self.write(name);
22177                    }
22178                }
22179            }
22180            ParameterStyle::DoubleAt => {
22181                self.write("@@");
22182                if let Some(ref name) = f.name {
22183                    self.write(name);
22184                }
22185            }
22186            ParameterStyle::DoubleDollar => {
22187                self.write("$$");
22188                if let Some(ref name) = f.name {
22189                    self.write(name);
22190                }
22191            }
22192            ParameterStyle::Percent => {
22193                if let Some(ref name) = f.name {
22194                    // %(name)s format
22195                    self.write("%(");
22196                    self.write(name);
22197                    self.write(")s");
22198                } else {
22199                    // %s format
22200                    self.write("%s");
22201                }
22202            }
22203            ParameterStyle::Brace => {
22204                // Spark/Databricks widget template variable: {name}
22205                // ClickHouse query parameter may include kind: {name: Type}
22206                self.write("{");
22207                if let Some(ref name) = f.name {
22208                    self.write(name);
22209                }
22210                if let Some(ref expr) = f.expression {
22211                    self.write(": ");
22212                    self.write(expr);
22213                }
22214                self.write("}");
22215            }
22216        }
22217        Ok(())
22218    }
22219
22220    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
22221        self.write("?");
22222        if let Some(idx) = f.index {
22223            self.write(&idx.to_string());
22224        }
22225        Ok(())
22226    }
22227
22228    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
22229        if f.is_block {
22230            self.write("/*");
22231            self.write(&f.text);
22232            self.write("*/");
22233        } else {
22234            self.write("--");
22235            self.write(&f.text);
22236        }
22237        Ok(())
22238    }
22239
22240    // Additional predicate generators
22241
22242    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
22243        self.generate_expression(&f.this)?;
22244        if f.not {
22245            self.write_space();
22246            self.write_keyword("NOT");
22247        }
22248        self.write_space();
22249        self.write_keyword("SIMILAR TO");
22250        self.write_space();
22251        self.generate_expression(&f.pattern)?;
22252        if let Some(ref escape) = f.escape {
22253            self.write_space();
22254            self.write_keyword("ESCAPE");
22255            self.write_space();
22256            self.generate_expression(escape)?;
22257        }
22258        Ok(())
22259    }
22260
22261    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
22262        self.generate_expression(&f.this)?;
22263        self.write_space();
22264        // Output comparison operator if present
22265        if let Some(op) = &f.op {
22266            match op {
22267                QuantifiedOp::Eq => self.write("="),
22268                QuantifiedOp::Neq => self.write("<>"),
22269                QuantifiedOp::Lt => self.write("<"),
22270                QuantifiedOp::Lte => self.write("<="),
22271                QuantifiedOp::Gt => self.write(">"),
22272                QuantifiedOp::Gte => self.write(">="),
22273            }
22274            self.write_space();
22275        }
22276        self.write_keyword(name);
22277
22278        // If the child is a Subquery, it provides its own parens — output with space
22279        if matches!(&f.subquery, Expression::Subquery(_)) {
22280            self.write_space();
22281            self.generate_expression(&f.subquery)?;
22282        } else {
22283            let is_statement = matches!(
22284                &f.subquery,
22285                Expression::Select(_)
22286                    | Expression::Union(_)
22287                    | Expression::Intersect(_)
22288                    | Expression::Except(_)
22289            );
22290            if is_statement
22291                && !self.config.quantified_no_paren_space
22292                && matches!(self.config.dialect, Some(DialectType::ClickHouse))
22293            {
22294                self.write_space();
22295            }
22296            self.write("(");
22297
22298            if self.config.pretty && is_statement {
22299                self.write_newline();
22300                self.indent_level += 1;
22301                self.write_indent();
22302            }
22303            self.generate_expression(&f.subquery)?;
22304            if self.config.pretty && is_statement {
22305                self.write_newline();
22306                self.indent_level -= 1;
22307                self.write_indent();
22308            }
22309            self.write(")");
22310        }
22311        Ok(())
22312    }
22313
22314    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
22315        // Check if this is a simple binary form (this OVERLAPS expression)
22316        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
22317            self.generate_expression(this)?;
22318            self.write_space();
22319            self.write_keyword("OVERLAPS");
22320            self.write_space();
22321            self.generate_expression(expr)?;
22322        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
22323            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
22324        {
22325            // Full ANSI form: (a, b) OVERLAPS (c, d)
22326            self.write("(");
22327            self.generate_expression(ls)?;
22328            self.write(", ");
22329            self.generate_expression(le)?;
22330            self.write(")");
22331            self.write_space();
22332            self.write_keyword("OVERLAPS");
22333            self.write_space();
22334            self.write("(");
22335            self.generate_expression(rs)?;
22336            self.write(", ");
22337            self.generate_expression(re)?;
22338            self.write(")");
22339        }
22340        Ok(())
22341    }
22342
22343    // Type conversion generators
22344
22345    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
22346        use crate::dialects::DialectType;
22347
22348        // SingleStore uses !:> syntax for try cast
22349        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
22350            self.generate_expression(&cast.this)?;
22351            self.write(" !:> ");
22352            self.generate_data_type(&cast.to)?;
22353            return Ok(());
22354        }
22355
22356        // Teradata uses TRYCAST (no underscore)
22357        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
22358            self.write_keyword("TRYCAST");
22359            self.write("(");
22360            self.generate_expression(&cast.this)?;
22361            self.write_space();
22362            self.write_keyword("AS");
22363            self.write_space();
22364            self.generate_data_type(&cast.to)?;
22365            self.write(")");
22366            return Ok(());
22367        }
22368
22369        // Dialects without TRY_CAST: generate as regular CAST
22370        let keyword = if matches!(
22371            self.config.dialect,
22372            Some(DialectType::Hive)
22373                | Some(DialectType::MySQL)
22374                | Some(DialectType::SQLite)
22375                | Some(DialectType::Oracle)
22376                | Some(DialectType::ClickHouse)
22377                | Some(DialectType::Redshift)
22378                | Some(DialectType::PostgreSQL)
22379                | Some(DialectType::StarRocks)
22380                | Some(DialectType::Doris)
22381        ) {
22382            "CAST"
22383        } else {
22384            "TRY_CAST"
22385        };
22386
22387        self.write_keyword(keyword);
22388        self.write("(");
22389        self.generate_expression(&cast.this)?;
22390        self.write_space();
22391        self.write_keyword("AS");
22392        self.write_space();
22393        self.generate_data_type(&cast.to)?;
22394
22395        // Output FORMAT clause if present
22396        if let Some(format) = &cast.format {
22397            self.write_space();
22398            self.write_keyword("FORMAT");
22399            self.write_space();
22400            self.generate_expression(format)?;
22401        }
22402
22403        self.write(")");
22404        Ok(())
22405    }
22406
22407    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
22408        self.write_keyword("SAFE_CAST");
22409        self.write("(");
22410        self.generate_expression(&cast.this)?;
22411        self.write_space();
22412        self.write_keyword("AS");
22413        self.write_space();
22414        self.generate_data_type(&cast.to)?;
22415
22416        // Output FORMAT clause if present
22417        if let Some(format) = &cast.format {
22418            self.write_space();
22419            self.write_keyword("FORMAT");
22420            self.write_space();
22421            self.generate_expression(format)?;
22422        }
22423
22424        self.write(")");
22425        Ok(())
22426    }
22427
22428    // Array/struct/map access generators
22429
22430    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
22431        // Wrap the base expression in parentheses when it uses arrow syntax (->)
22432        // which has lower precedence than bracket subscript ([]).
22433        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
22434        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
22435        if needs_parens {
22436            self.write("(");
22437        }
22438        self.generate_expression(&s.this)?;
22439        if needs_parens {
22440            self.write(")");
22441        }
22442        self.write("[");
22443        self.generate_expression(&s.index)?;
22444        self.write("]");
22445        Ok(())
22446    }
22447
22448    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
22449        self.generate_expression(&d.this)?;
22450        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
22451        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
22452        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
22453            && matches!(
22454                &d.this,
22455                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
22456            );
22457        if use_colon {
22458            self.write(":");
22459        } else {
22460            self.write(".");
22461        }
22462        self.generate_identifier(&d.field)
22463    }
22464
22465    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
22466        self.generate_expression(&m.this)?;
22467        self.write(".");
22468        // Method names after a dot should not be quoted based on reserved keywords
22469        // Only quote if explicitly marked as quoted in the AST
22470        if m.method.quoted {
22471            let q = self.config.identifier_quote;
22472            self.write(&format!("{}{}{}", q, m.method.name, q));
22473        } else {
22474            self.write(&m.method.name);
22475        }
22476        self.write("(");
22477        for (i, arg) in m.args.iter().enumerate() {
22478            if i > 0 {
22479                self.write(", ");
22480            }
22481            self.generate_expression(arg)?;
22482        }
22483        self.write(")");
22484        Ok(())
22485    }
22486
22487    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
22488        // Check if we need to wrap the inner expression in parentheses
22489        // JSON arrow expressions have lower precedence than array subscript
22490        let needs_parens = matches!(
22491            &s.this,
22492            Expression::JsonExtract(f) if f.arrow_syntax
22493        ) || matches!(
22494            &s.this,
22495            Expression::JsonExtractScalar(f) if f.arrow_syntax
22496        );
22497
22498        if needs_parens {
22499            self.write("(");
22500        }
22501        self.generate_expression(&s.this)?;
22502        if needs_parens {
22503            self.write(")");
22504        }
22505        self.write("[");
22506        if let Some(start) = &s.start {
22507            self.generate_expression(start)?;
22508        }
22509        self.write(":");
22510        if let Some(end) = &s.end {
22511            self.generate_expression(end)?;
22512        }
22513        self.write("]");
22514        Ok(())
22515    }
22516
22517    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22518        // Generate left expression, but skip trailing comments if they're already in left_comments
22519        // to avoid duplication (comments are captured as both expr.trailing_comments
22520        // and BinaryOp.left_comments during parsing)
22521        match &op.left {
22522            Expression::Column(col) => {
22523                // Generate column with trailing comments but skip them if they're
22524                // already captured in BinaryOp.left_comments to avoid duplication
22525                if let Some(table) = &col.table {
22526                    self.generate_identifier(table)?;
22527                    self.write(".");
22528                }
22529                self.generate_identifier(&col.name)?;
22530                // Oracle-style join marker (+)
22531                if col.join_mark && self.config.supports_column_join_marks {
22532                    self.write(" (+)");
22533                }
22534                // Output column trailing comments if they're not already in left_comments
22535                if op.left_comments.is_empty() {
22536                    for comment in &col.trailing_comments {
22537                        self.write_space();
22538                        self.write_formatted_comment(comment);
22539                    }
22540                }
22541            }
22542            Expression::Add(inner_op)
22543            | Expression::Sub(inner_op)
22544            | Expression::Mul(inner_op)
22545            | Expression::Div(inner_op)
22546            | Expression::Concat(inner_op) => {
22547                // Generate binary op without its trailing comments
22548                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22549                    Expression::Add(_) => "+",
22550                    Expression::Sub(_) => "-",
22551                    Expression::Mul(_) => "*",
22552                    Expression::Div(_) => "/",
22553                    Expression::Concat(_) => "||",
22554                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22555                })?;
22556            }
22557            _ => {
22558                self.generate_expression(&op.left)?;
22559            }
22560        }
22561        // Output comments after left operand
22562        for comment in &op.left_comments {
22563            self.write_space();
22564            self.write_formatted_comment(comment);
22565        }
22566        if self.config.pretty
22567            && matches!(self.config.dialect, Some(DialectType::Snowflake))
22568            && (operator == "AND" || operator == "OR")
22569        {
22570            self.write_newline();
22571            self.write_indent();
22572            self.write_keyword(operator);
22573        } else {
22574            self.write_space();
22575            if operator.chars().all(|c| c.is_alphabetic()) {
22576                self.write_keyword(operator);
22577            } else {
22578                self.write(operator);
22579            }
22580        }
22581        // Output comments after operator (before right operand)
22582        for comment in &op.operator_comments {
22583            self.write_space();
22584            self.write_formatted_comment(comment);
22585        }
22586        self.write_space();
22587        self.generate_expression(&op.right)?;
22588        // Output trailing comments after right operand
22589        for comment in &op.trailing_comments {
22590            self.write_space();
22591            self.write_formatted_comment(comment);
22592        }
22593        Ok(())
22594    }
22595
22596    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
22597        let keyword = connector.keyword();
22598        let Some(terms) = self.flatten_connector_terms(op, connector) else {
22599            return self.generate_binary_op(op, keyword);
22600        };
22601
22602        let wrap_clickhouse_or_term = |generator: &mut Self, term: &Expression| -> Result<()> {
22603            let should_wrap = matches!(connector, ConnectorOperator::Or)
22604                && matches!(generator.config.dialect, Some(DialectType::ClickHouse))
22605                && matches!(
22606                    generator.config.source_dialect,
22607                    Some(DialectType::ClickHouse)
22608                )
22609                && matches!(term, Expression::And(_));
22610            if should_wrap {
22611                generator.write("(");
22612                generator.generate_expression(term)?;
22613                generator.write(")");
22614            } else {
22615                generator.generate_expression(term)?;
22616            }
22617            Ok(())
22618        };
22619
22620        wrap_clickhouse_or_term(self, terms[0])?;
22621        for term in terms.iter().skip(1) {
22622            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
22623                self.write_newline();
22624                self.write_indent();
22625                self.write_keyword(keyword);
22626            } else {
22627                self.write_space();
22628                self.write_keyword(keyword);
22629            }
22630            self.write_space();
22631            wrap_clickhouse_or_term(self, term)?;
22632        }
22633
22634        Ok(())
22635    }
22636
22637    fn flatten_connector_terms<'a>(
22638        &self,
22639        root: &'a BinaryOp,
22640        connector: ConnectorOperator,
22641    ) -> Option<Vec<&'a Expression>> {
22642        if !root.left_comments.is_empty()
22643            || !root.operator_comments.is_empty()
22644            || !root.trailing_comments.is_empty()
22645        {
22646            return None;
22647        }
22648
22649        let mut terms = Vec::new();
22650        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
22651
22652        while let Some(expr) = stack.pop() {
22653            match (connector, expr) {
22654                (ConnectorOperator::And, Expression::And(inner))
22655                    if inner.left_comments.is_empty()
22656                        && inner.operator_comments.is_empty()
22657                        && inner.trailing_comments.is_empty() =>
22658                {
22659                    stack.push(&inner.right);
22660                    stack.push(&inner.left);
22661                }
22662                (ConnectorOperator::Or, Expression::Or(inner))
22663                    if inner.left_comments.is_empty()
22664                        && inner.operator_comments.is_empty()
22665                        && inner.trailing_comments.is_empty() =>
22666                {
22667                    stack.push(&inner.right);
22668                    stack.push(&inner.left);
22669                }
22670                _ => terms.push(expr),
22671            }
22672        }
22673
22674        if terms.len() > 1 {
22675            Some(terms)
22676        } else {
22677            None
22678        }
22679    }
22680
22681    fn missing_closing_parens_outside_quotes(sql: &str) -> usize {
22682        let mut depth = 0usize;
22683        let mut quote: Option<char> = None;
22684        let mut chars = sql.chars().peekable();
22685
22686        while let Some(ch) = chars.next() {
22687            if let Some(quote_char) = quote {
22688                if ch == '\\' {
22689                    chars.next();
22690                } else if ch == quote_char {
22691                    if quote_char == '\'' && chars.peek() == Some(&'\'') {
22692                        chars.next();
22693                    } else {
22694                        quote = None;
22695                    }
22696                }
22697                continue;
22698            }
22699
22700            match ch {
22701                '\'' | '"' | '`' => quote = Some(ch),
22702                '(' => depth += 1,
22703                ')' => depth = depth.saturating_sub(1),
22704                _ => {}
22705            }
22706        }
22707
22708        depth
22709    }
22710
22711    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
22712    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
22713        self.generate_like_op_inner(op, operator, false)
22714    }
22715
22716    fn generate_like_op_negated(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
22717        self.generate_like_op_inner(op, operator, true)
22718    }
22719
22720    fn generate_like_op_inner(&mut self, op: &LikeOp, operator: &str, negated: bool) -> Result<()> {
22721        if negated
22722            && matches!(
22723                self.config.dialect,
22724                Some(DialectType::ClickHouse)
22725                    | Some(DialectType::DataFusion)
22726                    | Some(DialectType::TSQL)
22727                    | Some(DialectType::Fabric)
22728            )
22729        {
22730            self.write_keyword("NOT");
22731            self.write_space();
22732            return self.generate_like_op_inner(op, operator, false);
22733        }
22734
22735        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22736            if let Expression::Star(star) = &op.left {
22737                if star
22738                    .except
22739                    .as_ref()
22740                    .is_some_and(|except| !except.is_empty())
22741                {
22742                    if let Some(table) = &star.table {
22743                        self.generate_identifier(table)?;
22744                        self.write(".");
22745                    }
22746                    self.write("*");
22747                    self.write_space();
22748                    self.write_keyword(operator);
22749                    if let Some(quantifier) = &op.quantifier {
22750                        self.write_space();
22751                        self.write_keyword(quantifier);
22752                        self.write_space();
22753                    } else {
22754                        self.write_space();
22755                    }
22756                    self.generate_expression(&op.right)?;
22757                    if let Some(escape) = &op.escape {
22758                        self.write_space();
22759                        self.write_keyword("ESCAPE");
22760                        self.write_space();
22761                        self.generate_expression(escape)?;
22762                    }
22763                    if let Some(except) = &star.except {
22764                        self.write_space();
22765                        self.write_keyword("EXCEPT");
22766                        self.write(" (");
22767                        for (i, col) in except.iter().enumerate() {
22768                            if i > 0 {
22769                                self.write(", ");
22770                            }
22771                            self.generate_identifier(col)?;
22772                        }
22773                        self.write(")");
22774                    }
22775                    return Ok(());
22776                }
22777            }
22778        }
22779
22780        self.generate_expression(&op.left)?;
22781        self.write_space();
22782        if negated {
22783            self.write_keyword("NOT");
22784            self.write_space();
22785        }
22786        // Drill backtick-quotes ILIKE
22787        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
22788            self.write("`ILIKE`");
22789        } else {
22790            self.write_keyword(operator);
22791        }
22792        if let Some(quantifier) = &op.quantifier {
22793            self.write_space();
22794            self.write_keyword(quantifier);
22795            // Match Python sqlglot behavior:
22796            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
22797            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
22798            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
22799            let is_any =
22800                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
22801            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
22802                self.write_space();
22803            }
22804        } else {
22805            self.write_space();
22806        }
22807        self.generate_expression(&op.right)?;
22808        if let Some(escape) = &op.escape {
22809            self.write_space();
22810            self.write_keyword("ESCAPE");
22811            self.write_space();
22812            self.generate_expression(escape)?;
22813        }
22814        Ok(())
22815    }
22816
22817    /// Generate null-safe equality
22818    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
22819    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
22820        use crate::dialects::DialectType;
22821        self.generate_expression(&op.left)?;
22822        self.write_space();
22823        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
22824            self.write("<=>");
22825        } else {
22826            self.write_keyword("IS NOT DISTINCT FROM");
22827        }
22828        self.write_space();
22829        self.generate_expression(&op.right)?;
22830        Ok(())
22831    }
22832
22833    /// Generate IS DISTINCT FROM (null-safe inequality)
22834    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
22835        self.generate_expression(&op.left)?;
22836        self.write_space();
22837        self.write_keyword("IS DISTINCT FROM");
22838        self.write_space();
22839        self.generate_expression(&op.right)?;
22840        Ok(())
22841    }
22842
22843    /// Generate binary op without trailing comments (used when nested inside another binary op)
22844    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22845        // Generate left expression, but skip trailing comments
22846        match &op.left {
22847            Expression::Column(col) => {
22848                if let Some(table) = &col.table {
22849                    self.generate_identifier(table)?;
22850                    self.write(".");
22851                }
22852                self.generate_identifier(&col.name)?;
22853                // Oracle-style join marker (+)
22854                if col.join_mark && self.config.supports_column_join_marks {
22855                    self.write(" (+)");
22856                }
22857            }
22858            Expression::Add(inner_op)
22859            | Expression::Sub(inner_op)
22860            | Expression::Mul(inner_op)
22861            | Expression::Div(inner_op)
22862            | Expression::Concat(inner_op) => {
22863                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22864                    Expression::Add(_) => "+",
22865                    Expression::Sub(_) => "-",
22866                    Expression::Mul(_) => "*",
22867                    Expression::Div(_) => "/",
22868                    Expression::Concat(_) => "||",
22869                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22870                })?;
22871            }
22872            _ => {
22873                self.generate_expression(&op.left)?;
22874            }
22875        }
22876        // Output left_comments
22877        for comment in &op.left_comments {
22878            self.write_space();
22879            self.write_formatted_comment(comment);
22880        }
22881        self.write_space();
22882        if operator.chars().all(|c| c.is_alphabetic()) {
22883            self.write_keyword(operator);
22884        } else {
22885            self.write(operator);
22886        }
22887        // Output operator_comments
22888        for comment in &op.operator_comments {
22889            self.write_space();
22890            self.write_formatted_comment(comment);
22891        }
22892        self.write_space();
22893        // Generate right expression, but skip trailing comments if it's a Column
22894        // (the parent's left_comments will output them)
22895        match &op.right {
22896            Expression::Column(col) => {
22897                if let Some(table) = &col.table {
22898                    self.generate_identifier(table)?;
22899                    self.write(".");
22900                }
22901                self.generate_identifier(&col.name)?;
22902                // Oracle-style join marker (+)
22903                if col.join_mark && self.config.supports_column_join_marks {
22904                    self.write(" (+)");
22905                }
22906            }
22907            _ => {
22908                self.generate_expression(&op.right)?;
22909            }
22910        }
22911        // Skip trailing_comments - parent will handle them via its left_comments
22912        Ok(())
22913    }
22914
22915    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
22916        if operator.chars().all(|c| c.is_alphabetic()) {
22917            self.write_keyword(operator);
22918            self.write_space();
22919        } else {
22920            self.write(operator);
22921            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
22922            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
22923                self.write_space();
22924            }
22925        }
22926        self.generate_expression(&op.this)
22927    }
22928
22929    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
22930        // Generic mode supports two styles for negated IN:
22931        // - Prefix: NOT a IN (...)
22932        // - Infix:  a NOT IN (...)
22933        let is_generic =
22934            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22935        let use_prefix_not =
22936            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
22937        if use_prefix_not {
22938            self.write_keyword("NOT");
22939            self.write_space();
22940        }
22941        self.generate_expression(&in_expr.this)?;
22942        if in_expr.global {
22943            self.write_space();
22944            self.write_keyword("GLOBAL");
22945        }
22946        if in_expr.not && !use_prefix_not {
22947            self.write_space();
22948            self.write_keyword("NOT");
22949        }
22950        self.write_space();
22951        self.write_keyword("IN");
22952
22953        // BigQuery: IN UNNEST(expr)
22954        if let Some(unnest_expr) = &in_expr.unnest {
22955            self.write_space();
22956            self.write_keyword("UNNEST");
22957            self.write("(");
22958            self.generate_expression(unnest_expr)?;
22959            self.write(")");
22960            return Ok(());
22961        }
22962
22963        if let Some(query) = &in_expr.query {
22964            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
22965            // vs a subquery (col IN (SELECT ...))
22966            let is_bare = in_expr.expressions.is_empty()
22967                && !matches!(
22968                    query,
22969                    Expression::Select(_)
22970                        | Expression::Union(_)
22971                        | Expression::Intersect(_)
22972                        | Expression::Except(_)
22973                        | Expression::Subquery(_)
22974                );
22975            if is_bare {
22976                // Bare identifier: no parentheses
22977                self.write_space();
22978                self.generate_expression(query)?;
22979            } else {
22980                // Subquery: with parentheses
22981                self.write(" (");
22982                let is_statement = matches!(
22983                    query,
22984                    Expression::Select(_)
22985                        | Expression::Union(_)
22986                        | Expression::Intersect(_)
22987                        | Expression::Except(_)
22988                        | Expression::Subquery(_)
22989                );
22990                if self.config.pretty && is_statement {
22991                    self.write_newline();
22992                    self.indent_level += 1;
22993                    self.write_indent();
22994                }
22995                self.generate_expression(query)?;
22996                if self.config.pretty && is_statement {
22997                    self.write_newline();
22998                    self.indent_level -= 1;
22999                    self.write_indent();
23000                }
23001                self.write(")");
23002            }
23003        } else {
23004            // DuckDB: IN without parentheses for single expression that is NOT a literal
23005            // (array/list membership like 'red' IN tbl.flags)
23006            // ClickHouse: IN without parentheses for single non-array expressions
23007            let is_duckdb = matches!(
23008                self.config.dialect,
23009                Some(crate::dialects::DialectType::DuckDB)
23010            );
23011            let is_clickhouse = matches!(
23012                self.config.dialect,
23013                Some(crate::dialects::DialectType::ClickHouse)
23014            );
23015            let single_expr = in_expr.expressions.len() == 1;
23016            if is_clickhouse && single_expr {
23017                if let Expression::Array(arr) = &in_expr.expressions[0] {
23018                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
23019                    self.write(" (");
23020                    for (i, expr) in arr.expressions.iter().enumerate() {
23021                        if i > 0 {
23022                            self.write(", ");
23023                        }
23024                        self.generate_expression(expr)?;
23025                    }
23026                    self.write(")");
23027                } else if in_expr.is_field {
23028                    self.write_space();
23029                    self.generate_expression(&in_expr.expressions[0])?;
23030                } else {
23031                    self.write(" (");
23032                    self.generate_expression(&in_expr.expressions[0])?;
23033                    self.write(")");
23034                }
23035            } else {
23036                let is_bare_ref = single_expr
23037                    && matches!(
23038                        &in_expr.expressions[0],
23039                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
23040                    );
23041                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
23042                    // Bare field reference (no parens in source): IN identifier
23043                    // Also DuckDB: IN without parentheses for array/list membership
23044                    self.write_space();
23045                    self.generate_expression(&in_expr.expressions[0])?;
23046                } else {
23047                    // Standard IN (list)
23048                    self.write(" (");
23049                    for (i, expr) in in_expr.expressions.iter().enumerate() {
23050                        if i > 0 {
23051                            self.write(", ");
23052                        }
23053                        self.generate_expression(expr)?;
23054                    }
23055                    self.write(")");
23056                }
23057            }
23058        }
23059
23060        Ok(())
23061    }
23062
23063    fn generate_between(&mut self, between: &Between) -> Result<()> {
23064        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
23065        let use_prefix_not = between.not
23066            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
23067        if use_prefix_not {
23068            self.write_keyword("NOT");
23069            self.write_space();
23070        }
23071        self.generate_expression(&between.this)?;
23072        if between.not && !use_prefix_not {
23073            self.write_space();
23074            self.write_keyword("NOT");
23075        }
23076        self.write_space();
23077        self.write_keyword("BETWEEN");
23078        // Emit SYMMETRIC/ASYMMETRIC if present
23079        if let Some(sym) = between.symmetric {
23080            if sym {
23081                self.write(" SYMMETRIC");
23082            } else {
23083                self.write(" ASYMMETRIC");
23084            }
23085        }
23086        self.write_space();
23087        self.generate_expression(&between.low)?;
23088        self.write_space();
23089        self.write_keyword("AND");
23090        self.write_space();
23091        self.generate_expression(&between.high)
23092    }
23093
23094    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
23095        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
23096        let use_prefix_not = is_null.not
23097            && (self.config.dialect.is_none()
23098                || self.config.dialect == Some(DialectType::Generic)
23099                || is_null.postfix_form);
23100        if use_prefix_not {
23101            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
23102            self.write_keyword("NOT");
23103            self.write_space();
23104            self.generate_expression(&is_null.this)?;
23105            self.write_space();
23106            self.write_keyword("IS");
23107            self.write_space();
23108            self.write_keyword("NULL");
23109        } else {
23110            self.generate_expression(&is_null.this)?;
23111            self.write_space();
23112            self.write_keyword("IS");
23113            if is_null.not {
23114                self.write_space();
23115                self.write_keyword("NOT");
23116            }
23117            self.write_space();
23118            self.write_keyword("NULL");
23119        }
23120        Ok(())
23121    }
23122
23123    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
23124        self.generate_expression(&is_true.this)?;
23125        self.write_space();
23126        self.write_keyword("IS");
23127        if is_true.not {
23128            self.write_space();
23129            self.write_keyword("NOT");
23130        }
23131        self.write_space();
23132        self.write_keyword("TRUE");
23133        Ok(())
23134    }
23135
23136    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
23137        self.generate_expression(&is_false.this)?;
23138        self.write_space();
23139        self.write_keyword("IS");
23140        if is_false.not {
23141            self.write_space();
23142            self.write_keyword("NOT");
23143        }
23144        self.write_space();
23145        self.write_keyword("FALSE");
23146        Ok(())
23147    }
23148
23149    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
23150        self.generate_expression(&is_json.this)?;
23151        self.write_space();
23152        self.write_keyword("IS");
23153        if is_json.negated {
23154            self.write_space();
23155            self.write_keyword("NOT");
23156        }
23157        self.write_space();
23158        self.write_keyword("JSON");
23159
23160        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
23161        if let Some(ref json_type) = is_json.json_type {
23162            self.write_space();
23163            self.write_keyword(json_type);
23164        }
23165
23166        // Output key uniqueness constraint if specified
23167        match &is_json.unique_keys {
23168            Some(JsonUniqueKeys::With) => {
23169                self.write_space();
23170                self.write_keyword("WITH UNIQUE KEYS");
23171            }
23172            Some(JsonUniqueKeys::Without) => {
23173                self.write_space();
23174                self.write_keyword("WITHOUT UNIQUE KEYS");
23175            }
23176            Some(JsonUniqueKeys::Shorthand) => {
23177                self.write_space();
23178                self.write_keyword("UNIQUE KEYS");
23179            }
23180            None => {}
23181        }
23182
23183        Ok(())
23184    }
23185
23186    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
23187        self.generate_expression(&is_expr.left)?;
23188        self.write_space();
23189        self.write_keyword("IS");
23190        self.write_space();
23191        self.generate_expression(&is_expr.right)
23192    }
23193
23194    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
23195        if exists.not {
23196            self.write_keyword("NOT");
23197            self.write_space();
23198        }
23199        self.write_keyword("EXISTS");
23200        self.write("(");
23201        let is_statement = matches!(
23202            &exists.this,
23203            Expression::Select(_)
23204                | Expression::Union(_)
23205                | Expression::Intersect(_)
23206                | Expression::Except(_)
23207        );
23208        if self.config.pretty && is_statement {
23209            self.write_newline();
23210            self.indent_level += 1;
23211            self.write_indent();
23212            self.generate_expression(&exists.this)?;
23213            self.write_newline();
23214            self.indent_level -= 1;
23215            self.write_indent();
23216            self.write(")");
23217        } else {
23218            self.generate_expression(&exists.this)?;
23219            self.write(")");
23220        }
23221        Ok(())
23222    }
23223
23224    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
23225        self.generate_expression(&op.left)?;
23226        self.write_space();
23227        self.write_keyword("MEMBER OF");
23228        self.write("(");
23229        self.generate_expression(&op.right)?;
23230        self.write(")");
23231        Ok(())
23232    }
23233
23234    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
23235        if subquery.lateral {
23236            self.write_keyword("LATERAL");
23237            self.write_space();
23238        }
23239
23240        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
23241        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
23242        // to carry the LIMIT modifier without adding more parens
23243        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
23244            matches!(
23245                &p.this,
23246                Expression::Select(_)
23247                    | Expression::Union(_)
23248                    | Expression::Intersect(_)
23249                    | Expression::Except(_)
23250                    | Expression::Subquery(_)
23251            )
23252        } else {
23253            false
23254        };
23255
23256        // Check if inner expression is a statement for pretty formatting
23257        let is_statement = matches!(
23258            &subquery.this,
23259            Expression::Select(_)
23260                | Expression::Union(_)
23261                | Expression::Intersect(_)
23262                | Expression::Except(_)
23263                | Expression::Merge(_)
23264        );
23265
23266        if !skip_outer_parens {
23267            self.write("(");
23268            if self.config.pretty && is_statement {
23269                self.write_newline();
23270                self.indent_level += 1;
23271                self.write_indent();
23272            }
23273        }
23274        self.generate_expression(&subquery.this)?;
23275
23276        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
23277        if subquery.modifiers_inside {
23278            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
23279            if let Some(order_by) = &subquery.order_by {
23280                self.write_space();
23281                self.write_keyword("ORDER BY");
23282                self.write_space();
23283                for (i, ord) in order_by.expressions.iter().enumerate() {
23284                    if i > 0 {
23285                        self.write(", ");
23286                    }
23287                    self.generate_ordered(ord)?;
23288                }
23289            }
23290
23291            if let Some(limit) = &subquery.limit {
23292                self.write_space();
23293                self.write_keyword("LIMIT");
23294                self.write_space();
23295                self.generate_expression(&limit.this)?;
23296                if limit.percent {
23297                    self.write_space();
23298                    self.write_keyword("PERCENT");
23299                }
23300            }
23301
23302            if let Some(offset) = &subquery.offset {
23303                self.write_space();
23304                self.write_keyword("OFFSET");
23305                self.write_space();
23306                self.generate_expression(&offset.this)?;
23307            }
23308        }
23309
23310        if !skip_outer_parens {
23311            if self.config.pretty && is_statement {
23312                self.write_newline();
23313                self.indent_level -= 1;
23314                self.write_indent();
23315            }
23316            self.write(")");
23317        }
23318
23319        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
23320        if !subquery.modifiers_inside {
23321            if let Some(order_by) = &subquery.order_by {
23322                self.write_space();
23323                self.write_keyword("ORDER BY");
23324                self.write_space();
23325                for (i, ord) in order_by.expressions.iter().enumerate() {
23326                    if i > 0 {
23327                        self.write(", ");
23328                    }
23329                    self.generate_ordered(ord)?;
23330                }
23331            }
23332
23333            if let Some(limit) = &subquery.limit {
23334                self.write_space();
23335                self.write_keyword("LIMIT");
23336                self.write_space();
23337                self.generate_expression(&limit.this)?;
23338                if limit.percent {
23339                    self.write_space();
23340                    self.write_keyword("PERCENT");
23341                }
23342            }
23343
23344            if let Some(offset) = &subquery.offset {
23345                self.write_space();
23346                self.write_keyword("OFFSET");
23347                self.write_space();
23348                self.generate_expression(&offset.this)?;
23349            }
23350
23351            // Generate DISTRIBUTE BY (Hive/Spark)
23352            if let Some(distribute_by) = &subquery.distribute_by {
23353                self.write_space();
23354                self.write_keyword("DISTRIBUTE BY");
23355                self.write_space();
23356                for (i, expr) in distribute_by.expressions.iter().enumerate() {
23357                    if i > 0 {
23358                        self.write(", ");
23359                    }
23360                    self.generate_expression(expr)?;
23361                }
23362            }
23363
23364            // Generate SORT BY (Hive/Spark)
23365            if let Some(sort_by) = &subquery.sort_by {
23366                self.write_space();
23367                self.write_keyword("SORT BY");
23368                self.write_space();
23369                for (i, ord) in sort_by.expressions.iter().enumerate() {
23370                    if i > 0 {
23371                        self.write(", ");
23372                    }
23373                    self.generate_ordered(ord)?;
23374                }
23375            }
23376
23377            // Generate CLUSTER BY (Hive/Spark)
23378            if let Some(cluster_by) = &subquery.cluster_by {
23379                self.write_space();
23380                self.write_keyword("CLUSTER BY");
23381                self.write_space();
23382                for (i, ord) in cluster_by.expressions.iter().enumerate() {
23383                    if i > 0 {
23384                        self.write(", ");
23385                    }
23386                    self.generate_ordered(ord)?;
23387                }
23388            }
23389        }
23390
23391        if let Some(alias) = &subquery.alias {
23392            self.write_space();
23393            let skip_as = matches!(self.config.dialect, Some(DialectType::Oracle))
23394                || (matches!(self.config.dialect, Some(DialectType::ClickHouse))
23395                    && !subquery.alias_explicit_as);
23396            if !skip_as {
23397                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23398                    self.write(subquery.alias_keyword.as_deref().unwrap_or("AS"));
23399                } else {
23400                    self.write_keyword("AS");
23401                }
23402                self.write_space();
23403            }
23404            self.generate_identifier(alias)?;
23405            if !subquery.column_aliases.is_empty() {
23406                self.write("(");
23407                for (i, col) in subquery.column_aliases.iter().enumerate() {
23408                    if i > 0 {
23409                        self.write(", ");
23410                    }
23411                    self.generate_identifier(col)?;
23412                }
23413                self.write(")");
23414            }
23415        }
23416        // Output trailing comments
23417        for comment in &subquery.trailing_comments {
23418            self.write(" ");
23419            self.write_formatted_comment(comment);
23420        }
23421        Ok(())
23422    }
23423
23424    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
23425        // Generate WITH clause if present
23426        if let Some(ref with) = pivot.with {
23427            self.generate_with(with)?;
23428            self.write_space();
23429        }
23430
23431        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
23432
23433        // Check for Redshift UNPIVOT in FROM clause:
23434        // UNPIVOT expr [AS val AT attr]
23435        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
23436        let is_redshift_unpivot = pivot.unpivot
23437            && pivot.expressions.is_empty()
23438            && pivot.fields.is_empty()
23439            && pivot.using.is_empty()
23440            && pivot.into.is_none()
23441            && !matches!(&pivot.this, Expression::Null(_));
23442
23443        if is_redshift_unpivot {
23444            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
23445            self.write_keyword("UNPIVOT");
23446            self.write_space();
23447            self.generate_expression(&pivot.this)?;
23448            // Alias - for Redshift it can be "val AT attr" format
23449            if let Some(alias) = &pivot.alias {
23450                self.write_space();
23451                self.write_keyword("AS");
23452                self.write_space();
23453                // The alias might contain " AT " for the attr part
23454                self.write(&alias.name);
23455            }
23456            return Ok(());
23457        }
23458
23459        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
23460        let is_simplified = !pivot.using.is_empty()
23461            || pivot.into.is_some()
23462            || (pivot.fields.is_empty()
23463                && !pivot.expressions.is_empty()
23464                && !matches!(&pivot.this, Expression::Null(_)));
23465
23466        if is_simplified {
23467            // DuckDB simplified syntax:
23468            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
23469            //   UNPIVOT table ON cols INTO NAME col VALUE col
23470            self.write_keyword(direction);
23471            self.write_space();
23472            self.generate_expression(&pivot.this)?;
23473
23474            if !pivot.expressions.is_empty() {
23475                self.write_space();
23476                self.write_keyword("ON");
23477                self.write_space();
23478                for (i, expr) in pivot.expressions.iter().enumerate() {
23479                    if i > 0 {
23480                        self.write(", ");
23481                    }
23482                    self.generate_expression(expr)?;
23483                }
23484            }
23485
23486            // INTO (for UNPIVOT)
23487            if let Some(into) = &pivot.into {
23488                self.write_space();
23489                self.write_keyword("INTO");
23490                self.write_space();
23491                self.generate_expression(into)?;
23492            }
23493
23494            // USING (for PIVOT)
23495            if !pivot.using.is_empty() {
23496                self.write_space();
23497                self.write_keyword("USING");
23498                self.write_space();
23499                for (i, expr) in pivot.using.iter().enumerate() {
23500                    if i > 0 {
23501                        self.write(", ");
23502                    }
23503                    self.generate_expression(expr)?;
23504                }
23505            }
23506
23507            // GROUP BY
23508            if let Some(group) = &pivot.group {
23509                self.write_space();
23510                self.generate_expression(group)?;
23511            }
23512        } else {
23513            // Standard syntax:
23514            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
23515            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
23516            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
23517            if !matches!(&pivot.this, Expression::Null(_)) {
23518                self.generate_expression(&pivot.this)?;
23519                self.write_space();
23520            }
23521            self.write_keyword(direction);
23522            self.write("(");
23523
23524            // Aggregation expressions
23525            for (i, expr) in pivot.expressions.iter().enumerate() {
23526                if i > 0 {
23527                    self.write(", ");
23528                }
23529                self.generate_expression(expr)?;
23530            }
23531
23532            // FOR...IN fields
23533            if !pivot.fields.is_empty() {
23534                if !pivot.expressions.is_empty() {
23535                    self.write_space();
23536                }
23537                self.write_keyword("FOR");
23538                self.write_space();
23539                for (i, field) in pivot.fields.iter().enumerate() {
23540                    if i > 0 {
23541                        self.write_space();
23542                    }
23543                    // field is an In expression: column IN (values)
23544                    self.generate_expression(field)?;
23545                }
23546            }
23547
23548            // DEFAULT ON NULL
23549            if let Some(default_val) = &pivot.default_on_null {
23550                self.write_space();
23551                self.write_keyword("DEFAULT ON NULL");
23552                self.write(" (");
23553                self.generate_expression(default_val)?;
23554                self.write(")");
23555            }
23556
23557            // GROUP BY inside PIVOT parens
23558            if let Some(group) = &pivot.group {
23559                self.write_space();
23560                self.generate_expression(group)?;
23561            }
23562
23563            self.write(")");
23564        }
23565
23566        // Alias
23567        if let Some(alias) = &pivot.alias {
23568            self.write_space();
23569            self.write_keyword("AS");
23570            self.write_space();
23571            self.generate_identifier(alias)?;
23572            self.generate_alias_column_list(&pivot.alias_columns)?;
23573        }
23574
23575        Ok(())
23576    }
23577
23578    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
23579        self.generate_expression(&unpivot.this)?;
23580        self.write_space();
23581        self.write_keyword("UNPIVOT");
23582        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
23583        if let Some(include) = unpivot.include_nulls {
23584            self.write_space();
23585            if include {
23586                self.write_keyword("INCLUDE NULLS");
23587            } else {
23588                self.write_keyword("EXCLUDE NULLS");
23589            }
23590            self.write_space();
23591        }
23592        self.write("(");
23593        if unpivot.value_column_parenthesized {
23594            self.write("(");
23595        }
23596        self.generate_identifier(&unpivot.value_column)?;
23597        // Output additional value columns if present
23598        for extra_col in &unpivot.extra_value_columns {
23599            self.write(", ");
23600            self.generate_identifier(extra_col)?;
23601        }
23602        if unpivot.value_column_parenthesized {
23603            self.write(")");
23604        }
23605        self.write_space();
23606        self.write_keyword("FOR");
23607        self.write_space();
23608        self.generate_identifier(&unpivot.name_column)?;
23609        self.write_space();
23610        self.write_keyword("IN");
23611        self.write(" (");
23612        for (i, col) in unpivot.columns.iter().enumerate() {
23613            if i > 0 {
23614                self.write(", ");
23615            }
23616            self.generate_expression(col)?;
23617        }
23618        self.write("))");
23619        if let Some(alias) = &unpivot.alias {
23620            self.write_space();
23621            self.write_keyword("AS");
23622            self.write_space();
23623            self.generate_identifier(alias)?;
23624            self.generate_alias_column_list(&unpivot.alias_columns)?;
23625        }
23626        Ok(())
23627    }
23628
23629    fn generate_alias_column_list(&mut self, columns: &[Identifier]) -> Result<()> {
23630        if columns.is_empty() {
23631            return Ok(());
23632        }
23633
23634        self.write("(");
23635        for (i, column) in columns.iter().enumerate() {
23636            if i > 0 {
23637                self.write(", ");
23638            }
23639            self.generate_identifier(column)?;
23640        }
23641        self.write(")");
23642        Ok(())
23643    }
23644
23645    fn generate_values(&mut self, values: &Values) -> Result<()> {
23646        self.write_keyword("VALUES");
23647        for (i, row) in values.expressions.iter().enumerate() {
23648            if i > 0 {
23649                self.write(",");
23650            }
23651            self.write(" (");
23652            for (j, expr) in row.expressions.iter().enumerate() {
23653                if j > 0 {
23654                    self.write(", ");
23655                }
23656                self.generate_expression(expr)?;
23657            }
23658            self.write(")");
23659        }
23660        if let Some(alias) = &values.alias {
23661            self.write_space();
23662            self.write_keyword("AS");
23663            self.write_space();
23664            self.generate_identifier(alias)?;
23665            if !values.column_aliases.is_empty() {
23666                self.write("(");
23667                for (i, col) in values.column_aliases.iter().enumerate() {
23668                    if i > 0 {
23669                        self.write(", ");
23670                    }
23671                    self.generate_identifier(col)?;
23672                }
23673                self.write(")");
23674            }
23675        }
23676        Ok(())
23677    }
23678
23679    fn generate_array(&mut self, arr: &Array) -> Result<()> {
23680        // Apply struct name inheritance for target dialects that need it
23681        let needs_inheritance = matches!(
23682            self.config.dialect,
23683            Some(DialectType::DuckDB)
23684                | Some(DialectType::Spark)
23685                | Some(DialectType::Databricks)
23686                | Some(DialectType::Hive)
23687                | Some(DialectType::Snowflake)
23688                | Some(DialectType::Presto)
23689                | Some(DialectType::Trino)
23690        );
23691        let propagated: Vec<Expression>;
23692        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
23693            propagated = Self::inherit_struct_field_names(&arr.expressions);
23694            &propagated
23695        } else {
23696            &arr.expressions
23697        };
23698
23699        // Generic mode: ARRAY(1, 2, 3) with parentheses
23700        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
23701        let use_parens =
23702            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
23703        if !self.config.array_bracket_only {
23704            self.write_keyword("ARRAY");
23705        }
23706        if use_parens {
23707            self.write("(");
23708        } else {
23709            self.write("[");
23710        }
23711        for (i, expr) in expressions.iter().enumerate() {
23712            if i > 0 {
23713                self.write(", ");
23714            }
23715            self.generate_expression(expr)?;
23716        }
23717        if use_parens {
23718            self.write(")");
23719        } else {
23720            self.write("]");
23721        }
23722        Ok(())
23723    }
23724
23725    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
23726        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
23727        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
23728        if tuple.expressions.len() == 2 {
23729            if let Expression::TableAlias(_) = &tuple.expressions[1] {
23730                // First element is the function/expression, second is the TableAlias
23731                self.generate_expression(&tuple.expressions[0])?;
23732                self.write_space();
23733                self.write_keyword("AS");
23734                self.write_space();
23735                self.generate_expression(&tuple.expressions[1])?;
23736                return Ok(());
23737            }
23738        }
23739
23740        // In pretty mode, format long tuples with each element on a new line
23741        // Only expand if total width exceeds threshold
23742        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
23743            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
23744            for expr in &tuple.expressions {
23745                expr_strings.push(self.generate_to_string(expr)?);
23746            }
23747            self.too_wide(&expr_strings)
23748        } else {
23749            false
23750        };
23751
23752        if expand_tuple {
23753            self.write("(");
23754            self.write_newline();
23755            self.indent_level += 1;
23756            for (i, expr) in tuple.expressions.iter().enumerate() {
23757                if i > 0 {
23758                    self.write(",");
23759                    self.write_newline();
23760                }
23761                self.write_indent();
23762                self.generate_expression(expr)?;
23763            }
23764            self.indent_level -= 1;
23765            self.write_newline();
23766            self.write_indent();
23767            self.write(")");
23768        } else {
23769            self.write("(");
23770            for (i, expr) in tuple.expressions.iter().enumerate() {
23771                if i > 0 {
23772                    self.write(", ");
23773                }
23774                self.generate_expression(expr)?;
23775            }
23776            self.write(")");
23777        }
23778        Ok(())
23779    }
23780
23781    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
23782        self.generate_expression(&pipe.this)?;
23783        self.write(" |> ");
23784        self.generate_expression(&pipe.expression)?;
23785        Ok(())
23786    }
23787
23788    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
23789        let unsupported_tsql_null_ordering = ordered.nulls_first.is_some()
23790            && !self.config.null_ordering_supported
23791            && matches!(
23792                self.config.dialect,
23793                Some(DialectType::TSQL) | Some(DialectType::Fabric)
23794            );
23795        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
23796        let emulate_tsql_null_ordering = if let Some(nulls_first) = ordered.nulls_first {
23797            let target_default_nulls_first = !ordered.desc;
23798
23799            unsupported_tsql_null_ordering
23800                && nulls_first != target_default_nulls_first
23801                && !random_ordering
23802        } else {
23803            false
23804        };
23805
23806        if emulate_tsql_null_ordering {
23807            self.write_keyword("CASE WHEN");
23808            self.write_space();
23809            self.generate_expression(&ordered.this)?;
23810            self.write_space();
23811            self.write_keyword("IS NULL THEN 1 ELSE 0 END");
23812            if ordered.nulls_first == Some(true) {
23813                self.write_space();
23814                self.write_keyword("DESC");
23815            }
23816            self.write(", ");
23817        }
23818
23819        self.generate_expression(&ordered.this)?;
23820        if ordered.desc {
23821            self.write_space();
23822            self.write_keyword("DESC");
23823        } else if ordered.explicit_asc {
23824            self.write_space();
23825            self.write_keyword("ASC");
23826        }
23827        if let Some(nulls_first) = ordered.nulls_first {
23828            if !unsupported_tsql_null_ordering
23829                && (self.config.null_ordering_supported
23830                    || !matches!(self.config.dialect, Some(DialectType::Fabric)))
23831            {
23832                // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
23833                // for the dialect. Different dialects have different NULL ordering defaults:
23834                //
23835                // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
23836                //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
23837                //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
23838                //
23839                // nulls_are_small (Spark, Hive, BigQuery, most others):
23840                //   - ASC: NULLS FIRST is default
23841                //   - DESC: NULLS LAST is default
23842                //
23843                // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
23844                //   - NULLS LAST is always the default regardless of sort direction
23845                let is_asc = !ordered.desc;
23846                let is_nulls_are_large = matches!(
23847                    self.config.dialect,
23848                    Some(DialectType::Oracle)
23849                        | Some(DialectType::PostgreSQL)
23850                        | Some(DialectType::Redshift)
23851                        | Some(DialectType::Snowflake)
23852                );
23853                let is_nulls_are_last = matches!(
23854                    self.config.dialect,
23855                    Some(DialectType::Dremio)
23856                        | Some(DialectType::DuckDB)
23857                        | Some(DialectType::Presto)
23858                        | Some(DialectType::Trino)
23859                        | Some(DialectType::Athena)
23860                        | Some(DialectType::ClickHouse)
23861                        | Some(DialectType::Drill)
23862                        | Some(DialectType::Exasol)
23863                );
23864
23865                // Check if the NULLS ordering matches the default for this dialect
23866                let is_default_nulls = if is_nulls_are_large {
23867                    // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
23868                    (is_asc && !nulls_first) || (!is_asc && nulls_first)
23869                } else if is_nulls_are_last {
23870                    // For nulls_are_last: NULLS LAST is always default
23871                    !nulls_first
23872                } else {
23873                    false
23874                };
23875
23876                if !is_default_nulls {
23877                    self.write_space();
23878                    self.write_keyword("NULLS");
23879                    self.write_space();
23880                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
23881                }
23882            }
23883        }
23884        // WITH FILL clause (ClickHouse)
23885        if let Some(ref with_fill) = ordered.with_fill {
23886            self.write_space();
23887            self.generate_with_fill(with_fill)?;
23888        }
23889        Ok(())
23890    }
23891
23892    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
23893    fn write_clickhouse_type(&mut self, type_str: &str) {
23894        if self.clickhouse_nullable_depth < 0 {
23895            // Map key context: don't wrap in Nullable
23896            self.write(type_str);
23897        } else {
23898            self.write(&format!("Nullable({})", type_str));
23899        }
23900    }
23901
23902    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
23903        use crate::dialects::DialectType;
23904
23905        match dt {
23906            DataType::Boolean => {
23907                // Dialect-specific boolean type mappings
23908                match self.config.dialect {
23909                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
23910                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
23911                    Some(DialectType::Oracle) => {
23912                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
23913                        self.write_keyword("NUMBER(1)")
23914                    }
23915                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
23916                    _ => self.write_keyword("BOOLEAN"),
23917                }
23918            }
23919            DataType::TinyInt { length } => {
23920                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
23921                // Dremio maps TINYINT to INT
23922                // ClickHouse maps TINYINT to Int8
23923                match self.config.dialect {
23924                    Some(DialectType::PostgreSQL)
23925                    | Some(DialectType::Redshift)
23926                    | Some(DialectType::Oracle)
23927                    | Some(DialectType::Exasol) => {
23928                        self.write_keyword("SMALLINT");
23929                    }
23930                    Some(DialectType::Teradata) => {
23931                        // Teradata uses BYTEINT for smallest integer
23932                        self.write_keyword("BYTEINT");
23933                    }
23934                    Some(DialectType::Dremio) => {
23935                        // Dremio maps TINYINT to INT
23936                        self.write_keyword("INT");
23937                    }
23938                    Some(DialectType::ClickHouse) => {
23939                        self.write_clickhouse_type("Int8");
23940                    }
23941                    _ => {
23942                        self.write_keyword("TINYINT");
23943                    }
23944                }
23945                if let Some(n) = length {
23946                    if !matches!(
23947                        self.config.dialect,
23948                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
23949                    ) {
23950                        self.write(&format!("({})", n));
23951                    }
23952                }
23953            }
23954            DataType::SmallInt { length } => {
23955                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
23956                match self.config.dialect {
23957                    Some(DialectType::Dremio) => {
23958                        self.write_keyword("INT");
23959                    }
23960                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
23961                        self.write_keyword("INTEGER");
23962                    }
23963                    Some(DialectType::BigQuery) => {
23964                        self.write_keyword("INT64");
23965                    }
23966                    Some(DialectType::ClickHouse) => {
23967                        self.write_clickhouse_type("Int16");
23968                    }
23969                    _ => {
23970                        self.write_keyword("SMALLINT");
23971                        if let Some(n) = length {
23972                            self.write(&format!("({})", n));
23973                        }
23974                    }
23975                }
23976            }
23977            DataType::Int {
23978                length,
23979                integer_spelling: _,
23980            } => {
23981                // BigQuery uses INT64 for INT
23982                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
23983                    self.write_keyword("INT64");
23984                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23985                    self.write_clickhouse_type("Int32");
23986                } else {
23987                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
23988                    let use_integer = match self.config.dialect {
23989                        Some(DialectType::TSQL)
23990                        | Some(DialectType::Fabric)
23991                        | Some(DialectType::Presto)
23992                        | Some(DialectType::Trino)
23993                        | Some(DialectType::SQLite)
23994                        | Some(DialectType::Redshift) => true,
23995                        _ => false,
23996                    };
23997                    if use_integer {
23998                        self.write_keyword("INTEGER");
23999                    } else {
24000                        self.write_keyword("INT");
24001                    }
24002                    if let Some(n) = length {
24003                        self.write(&format!("({})", n));
24004                    }
24005                }
24006            }
24007            DataType::BigInt { length } => {
24008                // Dialect-specific bigint type mappings
24009                match self.config.dialect {
24010                    Some(DialectType::Oracle) => {
24011                        // Oracle doesn't have BIGINT, uses INT
24012                        self.write_keyword("INT");
24013                    }
24014                    Some(DialectType::ClickHouse) => {
24015                        self.write_clickhouse_type("Int64");
24016                    }
24017                    _ => {
24018                        self.write_keyword("BIGINT");
24019                        if let Some(n) = length {
24020                            self.write(&format!("({})", n));
24021                        }
24022                    }
24023                }
24024            }
24025            DataType::Float {
24026                precision,
24027                scale,
24028                real_spelling,
24029            } => {
24030                // Dialect-specific float type mappings
24031                // If real_spelling is true, preserve REAL; otherwise use dialect default
24032                // Spark/Hive don't support REAL, always use FLOAT
24033                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24034                    self.write_clickhouse_type("Float32");
24035                } else if *real_spelling
24036                    && !matches!(
24037                        self.config.dialect,
24038                        Some(DialectType::Spark)
24039                            | Some(DialectType::Databricks)
24040                            | Some(DialectType::Hive)
24041                            | Some(DialectType::Snowflake)
24042                            | Some(DialectType::MySQL)
24043                            | Some(DialectType::BigQuery)
24044                    )
24045                {
24046                    self.write_keyword("REAL")
24047                } else {
24048                    match self.config.dialect {
24049                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
24050                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
24051                        _ => self.write_keyword("FLOAT"),
24052                    }
24053                }
24054                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
24055                // Spark/Hive don't support FLOAT(precision)
24056                if !matches!(
24057                    self.config.dialect,
24058                    Some(DialectType::Spark)
24059                        | Some(DialectType::Databricks)
24060                        | Some(DialectType::Hive)
24061                        | Some(DialectType::Presto)
24062                        | Some(DialectType::Trino)
24063                ) {
24064                    if let Some(p) = precision {
24065                        self.write(&format!("({}", p));
24066                        if let Some(s) = scale {
24067                            self.write(&format!(", {})", s));
24068                        } else {
24069                            self.write(")");
24070                        }
24071                    }
24072                }
24073            }
24074            DataType::Double { precision, scale } => {
24075                // Dialect-specific double type mappings
24076                match self.config.dialect {
24077                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24078                        self.write_keyword("FLOAT")
24079                    } // SQL Server/Fabric FLOAT is double
24080                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
24081                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
24082                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
24083                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
24084                    Some(DialectType::PostgreSQL)
24085                    | Some(DialectType::Redshift)
24086                    | Some(DialectType::Teradata)
24087                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
24088                    _ => self.write_keyword("DOUBLE"),
24089                }
24090                // MySQL supports DOUBLE(precision, scale)
24091                if let Some(p) = precision {
24092                    self.write(&format!("({}", p));
24093                    if let Some(s) = scale {
24094                        self.write(&format!(", {})", s));
24095                    } else {
24096                        self.write(")");
24097                    }
24098                }
24099            }
24100            DataType::Decimal { precision, scale } => {
24101                // Dialect-specific decimal type mappings
24102                match self.config.dialect {
24103                    Some(DialectType::ClickHouse) => {
24104                        self.write("Decimal");
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::Oracle) => {
24114                        // Oracle uses NUMBER instead of DECIMAL
24115                        self.write_keyword("NUMBER");
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                    Some(DialectType::BigQuery) => {
24125                        // BigQuery uses NUMERIC instead of DECIMAL
24126                        self.write_keyword("NUMERIC");
24127                        if let Some(p) = precision {
24128                            self.write(&format!("({}", p));
24129                            if let Some(s) = scale {
24130                                self.write(&format!(", {}", s));
24131                            }
24132                            self.write(")");
24133                        }
24134                    }
24135                    _ => {
24136                        self.write_keyword("DECIMAL");
24137                        if let Some(p) = precision {
24138                            self.write(&format!("({}", p));
24139                            if let Some(s) = scale {
24140                                self.write(&format!(", {}", s));
24141                            }
24142                            self.write(")");
24143                        }
24144                    }
24145                }
24146            }
24147            DataType::Char { length } => {
24148                // Dialect-specific char type mappings
24149                match self.config.dialect {
24150                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
24151                        // DuckDB/SQLite maps CHAR to TEXT
24152                        self.write_keyword("TEXT");
24153                    }
24154                    Some(DialectType::Hive)
24155                    | Some(DialectType::Spark)
24156                    | Some(DialectType::Databricks) => {
24157                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
24158                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
24159                        if length.is_some()
24160                            && !matches!(self.config.dialect, Some(DialectType::Hive))
24161                        {
24162                            self.write_keyword("CHAR");
24163                            if let Some(n) = length {
24164                                self.write(&format!("({})", n));
24165                            }
24166                        } else {
24167                            self.write_keyword("STRING");
24168                        }
24169                    }
24170                    Some(DialectType::Dremio) => {
24171                        // Dremio maps CHAR to VARCHAR
24172                        self.write_keyword("VARCHAR");
24173                        if let Some(n) = length {
24174                            self.write(&format!("({})", n));
24175                        }
24176                    }
24177                    _ => {
24178                        self.write_keyword("CHAR");
24179                        if let Some(n) = length {
24180                            self.write(&format!("({})", n));
24181                        }
24182                    }
24183                }
24184            }
24185            DataType::VarChar {
24186                length,
24187                parenthesized_length,
24188            } => {
24189                // Dialect-specific varchar type mappings
24190                match self.config.dialect {
24191                    Some(DialectType::Oracle) => {
24192                        self.write_keyword("VARCHAR2");
24193                        if let Some(n) = length {
24194                            self.write(&format!("({})", n));
24195                        }
24196                    }
24197                    Some(DialectType::DuckDB) => {
24198                        // DuckDB maps VARCHAR to TEXT, preserving length
24199                        self.write_keyword("TEXT");
24200                        if let Some(n) = length {
24201                            self.write(&format!("({})", n));
24202                        }
24203                    }
24204                    Some(DialectType::SQLite) => {
24205                        // SQLite maps VARCHAR to TEXT, preserving length
24206                        self.write_keyword("TEXT");
24207                        if let Some(n) = length {
24208                            self.write(&format!("({})", n));
24209                        }
24210                    }
24211                    Some(DialectType::MySQL) if length.is_none() => {
24212                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
24213                        self.write_keyword("TEXT");
24214                    }
24215                    Some(DialectType::Hive)
24216                    | Some(DialectType::Spark)
24217                    | Some(DialectType::Databricks)
24218                        if length.is_none() =>
24219                    {
24220                        // Hive/Spark/Databricks: VARCHAR without length → STRING
24221                        self.write_keyword("STRING");
24222                    }
24223                    _ => {
24224                        self.write_keyword("VARCHAR");
24225                        if let Some(n) = length {
24226                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
24227                            if *parenthesized_length {
24228                                self.write(&format!("(({}))", n));
24229                            } else {
24230                                self.write(&format!("({})", n));
24231                            }
24232                        }
24233                    }
24234                }
24235            }
24236            DataType::Text => {
24237                // Dialect-specific text type mappings
24238                match self.config.dialect {
24239                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
24240                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24241                        self.write_keyword("VARCHAR(MAX)")
24242                    }
24243                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
24244                    Some(DialectType::Snowflake)
24245                    | Some(DialectType::Dremio)
24246                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
24247                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
24248                    Some(DialectType::Presto)
24249                    | Some(DialectType::Trino)
24250                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
24251                    Some(DialectType::Spark)
24252                    | Some(DialectType::Databricks)
24253                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
24254                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
24255                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
24256                        self.write_keyword("STRING")
24257                    }
24258                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
24259                    _ => self.write_keyword("TEXT"),
24260                }
24261            }
24262            DataType::TextWithLength { length } => {
24263                // TEXT(n) - dialect-specific type with length
24264                match self.config.dialect {
24265                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
24266                    Some(DialectType::Hive)
24267                    | Some(DialectType::Spark)
24268                    | Some(DialectType::Databricks) => {
24269                        self.write(&format!("VARCHAR({})", length));
24270                    }
24271                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
24272                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
24273                    Some(DialectType::Snowflake)
24274                    | Some(DialectType::Presto)
24275                    | Some(DialectType::Trino)
24276                    | Some(DialectType::Athena)
24277                    | Some(DialectType::Drill)
24278                    | Some(DialectType::Dremio) => {
24279                        self.write(&format!("VARCHAR({})", length));
24280                    }
24281                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24282                        self.write(&format!("VARCHAR({})", length))
24283                    }
24284                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
24285                        self.write(&format!("STRING({})", length))
24286                    }
24287                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
24288                    _ => self.write(&format!("TEXT({})", length)),
24289                }
24290            }
24291            DataType::String { length } => {
24292                // STRING type with optional length (BigQuery STRING(n))
24293                match self.config.dialect {
24294                    Some(DialectType::ClickHouse) => {
24295                        // ClickHouse uses String with specific casing
24296                        self.write("String");
24297                        if let Some(n) = length {
24298                            self.write(&format!("({})", n));
24299                        }
24300                    }
24301                    Some(DialectType::BigQuery)
24302                    | Some(DialectType::Hive)
24303                    | Some(DialectType::Spark)
24304                    | Some(DialectType::Databricks)
24305                    | Some(DialectType::StarRocks)
24306                    | Some(DialectType::Doris) => {
24307                        self.write_keyword("STRING");
24308                        if let Some(n) = length {
24309                            self.write(&format!("({})", n));
24310                        }
24311                    }
24312                    Some(DialectType::PostgreSQL) => {
24313                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
24314                        if let Some(n) = length {
24315                            self.write_keyword("VARCHAR");
24316                            self.write(&format!("({})", n));
24317                        } else {
24318                            self.write_keyword("TEXT");
24319                        }
24320                    }
24321                    Some(DialectType::Redshift) => {
24322                        // Redshift: STRING -> VARCHAR(MAX)
24323                        if let Some(n) = length {
24324                            self.write_keyword("VARCHAR");
24325                            self.write(&format!("({})", n));
24326                        } else {
24327                            self.write_keyword("VARCHAR(MAX)");
24328                        }
24329                    }
24330                    Some(DialectType::MySQL) => {
24331                        // MySQL doesn't have STRING - use VARCHAR or TEXT
24332                        if let Some(n) = length {
24333                            self.write_keyword("VARCHAR");
24334                            self.write(&format!("({})", n));
24335                        } else {
24336                            self.write_keyword("TEXT");
24337                        }
24338                    }
24339                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24340                        // TSQL: STRING -> VARCHAR(MAX)
24341                        if let Some(n) = length {
24342                            self.write_keyword("VARCHAR");
24343                            self.write(&format!("({})", n));
24344                        } else {
24345                            self.write_keyword("VARCHAR(MAX)");
24346                        }
24347                    }
24348                    Some(DialectType::Oracle) => {
24349                        // Oracle: STRING -> CLOB
24350                        self.write_keyword("CLOB");
24351                    }
24352                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
24353                        // DuckDB/Materialize uses TEXT for string types
24354                        self.write_keyword("TEXT");
24355                        if let Some(n) = length {
24356                            self.write(&format!("({})", n));
24357                        }
24358                    }
24359                    Some(DialectType::Presto)
24360                    | Some(DialectType::Trino)
24361                    | Some(DialectType::Drill)
24362                    | Some(DialectType::Dremio) => {
24363                        // Presto/Trino/Drill use VARCHAR for string types
24364                        self.write_keyword("VARCHAR");
24365                        if let Some(n) = length {
24366                            self.write(&format!("({})", n));
24367                        }
24368                    }
24369                    Some(DialectType::Snowflake) => {
24370                        // Snowflake: STRING stays as STRING (identity/DDL)
24371                        // CAST context STRING -> VARCHAR is handled in generate_cast
24372                        self.write_keyword("STRING");
24373                        if let Some(n) = length {
24374                            self.write(&format!("({})", n));
24375                        }
24376                    }
24377                    _ => {
24378                        // Default: output STRING with optional length
24379                        self.write_keyword("STRING");
24380                        if let Some(n) = length {
24381                            self.write(&format!("({})", n));
24382                        }
24383                    }
24384                }
24385            }
24386            DataType::Binary { length } => {
24387                // Dialect-specific binary type mappings
24388                match self.config.dialect {
24389                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
24390                        self.write_keyword("BYTEA");
24391                        if let Some(n) = length {
24392                            self.write(&format!("({})", n));
24393                        }
24394                    }
24395                    Some(DialectType::Redshift) => {
24396                        self.write_keyword("VARBYTE");
24397                        if let Some(n) = length {
24398                            self.write(&format!("({})", n));
24399                        }
24400                    }
24401                    Some(DialectType::DuckDB)
24402                    | Some(DialectType::SQLite)
24403                    | Some(DialectType::Oracle) => {
24404                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
24405                        self.write_keyword("BLOB");
24406                        if let Some(n) = length {
24407                            self.write(&format!("({})", n));
24408                        }
24409                    }
24410                    Some(DialectType::Presto)
24411                    | Some(DialectType::Trino)
24412                    | Some(DialectType::Athena)
24413                    | Some(DialectType::Drill)
24414                    | Some(DialectType::Dremio) => {
24415                        // These dialects map BINARY to VARBINARY
24416                        self.write_keyword("VARBINARY");
24417                        if let Some(n) = length {
24418                            self.write(&format!("({})", n));
24419                        }
24420                    }
24421                    Some(DialectType::ClickHouse) => {
24422                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
24423                        if self.clickhouse_nullable_depth < 0 {
24424                            self.write("BINARY");
24425                        } else {
24426                            self.write("Nullable(BINARY");
24427                        }
24428                        if let Some(n) = length {
24429                            self.write(&format!("({})", n));
24430                        }
24431                        if self.clickhouse_nullable_depth >= 0 {
24432                            self.write(")");
24433                        }
24434                    }
24435                    _ => {
24436                        self.write_keyword("BINARY");
24437                        if let Some(n) = length {
24438                            self.write(&format!("({})", n));
24439                        }
24440                    }
24441                }
24442            }
24443            DataType::VarBinary { length } => {
24444                // Dialect-specific varbinary type mappings
24445                match self.config.dialect {
24446                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
24447                        self.write_keyword("BYTEA");
24448                        if let Some(n) = length {
24449                            self.write(&format!("({})", n));
24450                        }
24451                    }
24452                    Some(DialectType::Redshift) => {
24453                        self.write_keyword("VARBYTE");
24454                        if let Some(n) = length {
24455                            self.write(&format!("({})", n));
24456                        }
24457                    }
24458                    Some(DialectType::DuckDB)
24459                    | Some(DialectType::SQLite)
24460                    | Some(DialectType::Oracle) => {
24461                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
24462                        self.write_keyword("BLOB");
24463                        if let Some(n) = length {
24464                            self.write(&format!("({})", n));
24465                        }
24466                    }
24467                    Some(DialectType::Exasol) => {
24468                        // Exasol maps VARBINARY to VARCHAR
24469                        self.write_keyword("VARCHAR");
24470                    }
24471                    Some(DialectType::Spark)
24472                    | Some(DialectType::Hive)
24473                    | Some(DialectType::Databricks) => {
24474                        // Spark/Hive use BINARY instead of VARBINARY
24475                        self.write_keyword("BINARY");
24476                        if let Some(n) = length {
24477                            self.write(&format!("({})", n));
24478                        }
24479                    }
24480                    Some(DialectType::ClickHouse) => {
24481                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
24482                        self.write_clickhouse_type("String");
24483                    }
24484                    _ => {
24485                        self.write_keyword("VARBINARY");
24486                        if let Some(n) = length {
24487                            self.write(&format!("({})", n));
24488                        }
24489                    }
24490                }
24491            }
24492            DataType::Blob => {
24493                // Dialect-specific blob type mappings
24494                match self.config.dialect {
24495                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
24496                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
24497                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24498                        self.write_keyword("VARBINARY")
24499                    }
24500                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
24501                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
24502                    Some(DialectType::Presto)
24503                    | Some(DialectType::Trino)
24504                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
24505                    Some(DialectType::DuckDB) => {
24506                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
24507                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
24508                        self.write_keyword("VARBINARY");
24509                    }
24510                    Some(DialectType::Spark)
24511                    | Some(DialectType::Databricks)
24512                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
24513                    Some(DialectType::ClickHouse) => {
24514                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
24515                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
24516                        // This matches Python sqlglot behavior.
24517                        self.write("Nullable(String)");
24518                    }
24519                    _ => self.write_keyword("BLOB"),
24520                }
24521            }
24522            DataType::Bit { length } => {
24523                // Dialect-specific bit type mappings
24524                match self.config.dialect {
24525                    Some(DialectType::Dremio)
24526                    | Some(DialectType::Spark)
24527                    | Some(DialectType::Databricks)
24528                    | Some(DialectType::Hive)
24529                    | Some(DialectType::Snowflake)
24530                    | Some(DialectType::BigQuery)
24531                    | Some(DialectType::Presto)
24532                    | Some(DialectType::Trino)
24533                    | Some(DialectType::ClickHouse)
24534                    | Some(DialectType::Redshift) => {
24535                        // These dialects don't support BIT type, use BOOLEAN
24536                        self.write_keyword("BOOLEAN");
24537                    }
24538                    _ => {
24539                        self.write_keyword("BIT");
24540                        if let Some(n) = length {
24541                            self.write(&format!("({})", n));
24542                        }
24543                    }
24544                }
24545            }
24546            DataType::VarBit { length } => {
24547                self.write_keyword("VARBIT");
24548                if let Some(n) = length {
24549                    self.write(&format!("({})", n));
24550                }
24551            }
24552            DataType::Date => self.write_keyword("DATE"),
24553            DataType::Time {
24554                precision,
24555                timezone,
24556            } => {
24557                if *timezone {
24558                    // Dialect-specific TIME WITH TIME ZONE output
24559                    match self.config.dialect {
24560                        Some(DialectType::DuckDB) => {
24561                            // DuckDB: TIMETZ (drops precision)
24562                            self.write_keyword("TIMETZ");
24563                        }
24564                        Some(DialectType::PostgreSQL) => {
24565                            // PostgreSQL: TIMETZ or TIMETZ(p)
24566                            self.write_keyword("TIMETZ");
24567                            if let Some(p) = precision {
24568                                self.write(&format!("({})", p));
24569                            }
24570                        }
24571                        _ => {
24572                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
24573                            self.write_keyword("TIME");
24574                            if let Some(p) = precision {
24575                                self.write(&format!("({})", p));
24576                            }
24577                            self.write_keyword(" WITH TIME ZONE");
24578                        }
24579                    }
24580                } else {
24581                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
24582                    if matches!(
24583                        self.config.dialect,
24584                        Some(DialectType::Spark)
24585                            | Some(DialectType::Databricks)
24586                            | Some(DialectType::Hive)
24587                    ) {
24588                        self.write_keyword("TIMESTAMP");
24589                    } else {
24590                        self.write_keyword("TIME");
24591                        if let Some(p) = precision {
24592                            self.write(&format!("({})", p));
24593                        }
24594                    }
24595                }
24596            }
24597            DataType::Timestamp {
24598                precision,
24599                timezone,
24600            } => {
24601                // Dialect-specific timestamp type mappings
24602                match self.config.dialect {
24603                    Some(DialectType::Snowflake) if *timezone => {
24604                        self.write_keyword("TIMESTAMPTZ");
24605                        if let Some(p) = precision {
24606                            self.write(&format!("({})", p));
24607                        }
24608                    }
24609                    Some(DialectType::ClickHouse) => {
24610                        self.write("DateTime");
24611                        if let Some(p) = precision {
24612                            self.write(&format!("({})", p));
24613                        }
24614                    }
24615                    Some(DialectType::TSQL) => {
24616                        if *timezone {
24617                            self.write_keyword("DATETIMEOFFSET");
24618                        } else {
24619                            self.write_keyword("DATETIME2");
24620                        }
24621                        if let Some(p) = precision {
24622                            self.write(&format!("({})", p));
24623                        }
24624                    }
24625                    Some(DialectType::MySQL) => {
24626                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
24627                        self.write_keyword("TIMESTAMP");
24628                        if let Some(p) = precision {
24629                            self.write(&format!("({})", p));
24630                        }
24631                    }
24632                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
24633                        // Doris/StarRocks: TIMESTAMP -> DATETIME
24634                        self.write_keyword("DATETIME");
24635                        if let Some(p) = precision {
24636                            self.write(&format!("({})", p));
24637                        }
24638                    }
24639                    Some(DialectType::BigQuery) => {
24640                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
24641                        if *timezone {
24642                            self.write_keyword("TIMESTAMP");
24643                        } else {
24644                            self.write_keyword("DATETIME");
24645                        }
24646                    }
24647                    Some(DialectType::DuckDB) => {
24648                        // DuckDB: TIMESTAMPTZ shorthand
24649                        if *timezone {
24650                            self.write_keyword("TIMESTAMPTZ");
24651                        } else {
24652                            self.write_keyword("TIMESTAMP");
24653                            if let Some(p) = precision {
24654                                self.write(&format!("({})", p));
24655                            }
24656                        }
24657                    }
24658                    _ => {
24659                        if *timezone && !self.config.tz_to_with_time_zone {
24660                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
24661                            self.write_keyword("TIMESTAMPTZ");
24662                            if let Some(p) = precision {
24663                                self.write(&format!("({})", p));
24664                            }
24665                        } else {
24666                            self.write_keyword("TIMESTAMP");
24667                            if let Some(p) = precision {
24668                                self.write(&format!("({})", p));
24669                            }
24670                            if *timezone {
24671                                self.write_space();
24672                                self.write_keyword("WITH TIME ZONE");
24673                            }
24674                        }
24675                    }
24676                }
24677            }
24678            DataType::Interval { unit, to } => {
24679                self.write_keyword("INTERVAL");
24680                if let Some(u) = unit {
24681                    self.write_space();
24682                    self.write_keyword(u);
24683                }
24684                // Handle range intervals like DAY TO HOUR
24685                if let Some(t) = to {
24686                    self.write_space();
24687                    self.write_keyword("TO");
24688                    self.write_space();
24689                    self.write_keyword(t);
24690                }
24691            }
24692            DataType::Json => {
24693                // Dialect-specific JSON type mappings
24694                match self.config.dialect {
24695                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
24696                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
24697                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
24698                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24699                    _ => self.write_keyword("JSON"),
24700                }
24701            }
24702            DataType::JsonB => {
24703                // JSONB is PostgreSQL specific, but Doris also supports it
24704                match self.config.dialect {
24705                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
24706                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
24707                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24708                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24709                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
24710                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
24711                }
24712            }
24713            DataType::Uuid => {
24714                // Dialect-specific UUID type mappings
24715                match self.config.dialect {
24716                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
24717                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
24718                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
24719                    Some(DialectType::BigQuery)
24720                    | Some(DialectType::Spark)
24721                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
24722                    _ => self.write_keyword("UUID"),
24723                }
24724            }
24725            DataType::Array {
24726                element_type,
24727                dimension,
24728            } => {
24729                // Dialect-specific array syntax
24730                match self.config.dialect {
24731                    Some(DialectType::PostgreSQL)
24732                    | Some(DialectType::Redshift)
24733                    | Some(DialectType::DuckDB) => {
24734                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
24735                        self.generate_data_type(element_type)?;
24736                        if let Some(dim) = dimension {
24737                            self.write(&format!("[{}]", dim));
24738                        } else {
24739                            self.write("[]");
24740                        }
24741                    }
24742                    Some(DialectType::BigQuery) => {
24743                        self.write_keyword("ARRAY<");
24744                        self.generate_data_type(element_type)?;
24745                        self.write(">");
24746                    }
24747                    Some(DialectType::Snowflake)
24748                    | Some(DialectType::Presto)
24749                    | Some(DialectType::Trino)
24750                    | Some(DialectType::ClickHouse) => {
24751                        // These dialects use Array(TYPE) parentheses syntax
24752                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24753                            self.write("Array(");
24754                        } else {
24755                            self.write_keyword("ARRAY(");
24756                        }
24757                        self.generate_data_type(element_type)?;
24758                        self.write(")");
24759                    }
24760                    Some(DialectType::TSQL)
24761                    | Some(DialectType::MySQL)
24762                    | Some(DialectType::Oracle) => {
24763                        // These dialects don't have native array types
24764                        // Fall back to JSON or use native workarounds
24765                        match self.config.dialect {
24766                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
24767                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24768                            _ => self.write_keyword("JSON"),
24769                        }
24770                    }
24771                    _ => {
24772                        // Default: use angle bracket syntax (ARRAY<T>)
24773                        self.write_keyword("ARRAY<");
24774                        self.generate_data_type(element_type)?;
24775                        self.write(">");
24776                    }
24777                }
24778            }
24779            DataType::List { element_type } => {
24780                // Materialize: element_type LIST (postfix syntax)
24781                self.generate_data_type(element_type)?;
24782                self.write_keyword(" LIST");
24783            }
24784            DataType::Map {
24785                key_type,
24786                value_type,
24787            } => {
24788                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
24789                match self.config.dialect {
24790                    Some(DialectType::Materialize) => {
24791                        // Materialize: MAP[key_type => value_type]
24792                        self.write_keyword("MAP[");
24793                        self.generate_data_type(key_type)?;
24794                        self.write(" => ");
24795                        self.generate_data_type(value_type)?;
24796                        self.write("]");
24797                    }
24798                    Some(DialectType::Snowflake)
24799                    | Some(DialectType::RisingWave)
24800                    | Some(DialectType::DuckDB)
24801                    | Some(DialectType::Presto)
24802                    | Some(DialectType::Trino)
24803                    | Some(DialectType::Athena) => {
24804                        self.write_keyword("MAP(");
24805                        self.generate_data_type(key_type)?;
24806                        self.write(", ");
24807                        self.generate_data_type(value_type)?;
24808                        self.write(")");
24809                    }
24810                    Some(DialectType::ClickHouse) => {
24811                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
24812                        // Key types must NOT be wrapped in Nullable
24813                        self.write("Map(");
24814                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
24815                        self.generate_data_type(key_type)?;
24816                        self.clickhouse_nullable_depth = 0;
24817                        self.write(", ");
24818                        self.generate_data_type(value_type)?;
24819                        self.write(")");
24820                    }
24821                    _ => {
24822                        self.write_keyword("MAP<");
24823                        self.generate_data_type(key_type)?;
24824                        self.write(", ");
24825                        self.generate_data_type(value_type)?;
24826                        self.write(">");
24827                    }
24828                }
24829            }
24830            DataType::Vector {
24831                element_type,
24832                dimension,
24833            } => {
24834                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
24835                    // SingleStore format: VECTOR(dimension, type_alias)
24836                    self.write_keyword("VECTOR(");
24837                    if let Some(dim) = dimension {
24838                        self.write(&dim.to_string());
24839                    }
24840                    // Map type back to SingleStore alias
24841                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
24842                        DataType::TinyInt { .. } => Some("I8"),
24843                        DataType::SmallInt { .. } => Some("I16"),
24844                        DataType::Int { .. } => Some("I32"),
24845                        DataType::BigInt { .. } => Some("I64"),
24846                        DataType::Float { .. } => Some("F32"),
24847                        DataType::Double { .. } => Some("F64"),
24848                        _ => None,
24849                    });
24850                    if let Some(alias) = type_alias {
24851                        if dimension.is_some() {
24852                            self.write(", ");
24853                        }
24854                        self.write(alias);
24855                    }
24856                    self.write(")");
24857                } else {
24858                    // Snowflake format: VECTOR(type, dimension)
24859                    self.write_keyword("VECTOR(");
24860                    if let Some(ref et) = element_type {
24861                        self.generate_data_type(et)?;
24862                        if dimension.is_some() {
24863                            self.write(", ");
24864                        }
24865                    }
24866                    if let Some(dim) = dimension {
24867                        self.write(&dim.to_string());
24868                    }
24869                    self.write(")");
24870                }
24871            }
24872            DataType::Object { fields, modifier } => {
24873                self.write_keyword("OBJECT(");
24874                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
24875                    if i > 0 {
24876                        self.write(", ");
24877                    }
24878                    self.write(name);
24879                    self.write(" ");
24880                    self.generate_data_type(dt)?;
24881                    if *not_null {
24882                        self.write_keyword(" NOT NULL");
24883                    }
24884                }
24885                self.write(")");
24886                if let Some(mod_str) = modifier {
24887                    self.write(" ");
24888                    self.write_keyword(mod_str);
24889                }
24890            }
24891            DataType::Struct { fields, nested } => {
24892                // Dialect-specific struct type mappings
24893                match self.config.dialect {
24894                    Some(DialectType::Snowflake) => {
24895                        // Snowflake maps STRUCT to OBJECT
24896                        self.write_keyword("OBJECT(");
24897                        for (i, field) in fields.iter().enumerate() {
24898                            if i > 0 {
24899                                self.write(", ");
24900                            }
24901                            if !field.name.is_empty() {
24902                                self.write(&field.name);
24903                                self.write(" ");
24904                            }
24905                            self.generate_data_type(&field.data_type)?;
24906                        }
24907                        self.write(")");
24908                    }
24909                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
24910                        // Presto/Trino use ROW(name TYPE, ...) syntax
24911                        self.write_keyword("ROW(");
24912                        for (i, field) in fields.iter().enumerate() {
24913                            if i > 0 {
24914                                self.write(", ");
24915                            }
24916                            if !field.name.is_empty() {
24917                                self.write(&field.name);
24918                                self.write(" ");
24919                            }
24920                            self.generate_data_type(&field.data_type)?;
24921                        }
24922                        self.write(")");
24923                    }
24924                    Some(DialectType::DuckDB) => {
24925                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
24926                        self.write_keyword("STRUCT(");
24927                        for (i, field) in fields.iter().enumerate() {
24928                            if i > 0 {
24929                                self.write(", ");
24930                            }
24931                            if !field.name.is_empty() {
24932                                self.write(&field.name);
24933                                self.write(" ");
24934                            }
24935                            self.generate_data_type(&field.data_type)?;
24936                        }
24937                        self.write(")");
24938                    }
24939                    Some(DialectType::ClickHouse) => {
24940                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
24941                        self.write("Tuple(");
24942                        for (i, field) in fields.iter().enumerate() {
24943                            if i > 0 {
24944                                self.write(", ");
24945                            }
24946                            if !field.name.is_empty() {
24947                                self.write(&field.name);
24948                                self.write(" ");
24949                            }
24950                            self.generate_data_type(&field.data_type)?;
24951                        }
24952                        self.write(")");
24953                    }
24954                    Some(DialectType::SingleStore) => {
24955                        // SingleStore uses RECORD(name TYPE, ...) for struct types
24956                        self.write_keyword("RECORD(");
24957                        for (i, field) in fields.iter().enumerate() {
24958                            if i > 0 {
24959                                self.write(", ");
24960                            }
24961                            if !field.name.is_empty() {
24962                                self.write(&field.name);
24963                                self.write(" ");
24964                            }
24965                            self.generate_data_type(&field.data_type)?;
24966                        }
24967                        self.write(")");
24968                    }
24969                    _ => {
24970                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
24971                        let force_angle_brackets = matches!(
24972                            self.config.dialect,
24973                            Some(DialectType::Hive)
24974                                | Some(DialectType::Spark)
24975                                | Some(DialectType::Databricks)
24976                        );
24977                        if *nested && !force_angle_brackets {
24978                            self.write_keyword("STRUCT(");
24979                            for (i, field) in fields.iter().enumerate() {
24980                                if i > 0 {
24981                                    self.write(", ");
24982                                }
24983                                if !field.name.is_empty() {
24984                                    self.write(&field.name);
24985                                    self.write(" ");
24986                                }
24987                                self.generate_data_type(&field.data_type)?;
24988                            }
24989                            self.write(")");
24990                        } else {
24991                            self.write_keyword("STRUCT<");
24992                            for (i, field) in fields.iter().enumerate() {
24993                                if i > 0 {
24994                                    self.write(", ");
24995                                }
24996                                if !field.name.is_empty() {
24997                                    // Named field: name TYPE (with configurable separator for Hive)
24998                                    self.write(&field.name);
24999                                    self.write(self.config.struct_field_sep);
25000                                }
25001                                // For anonymous fields, just output the type
25002                                self.generate_data_type(&field.data_type)?;
25003                                // Spark/Databricks: Output COMMENT clause if present
25004                                if let Some(comment) = &field.comment {
25005                                    self.write(" COMMENT '");
25006                                    self.write(comment);
25007                                    self.write("'");
25008                                }
25009                                // BigQuery: Output OPTIONS clause if present
25010                                if !field.options.is_empty() {
25011                                    self.write(" ");
25012                                    self.generate_options_clause(&field.options)?;
25013                                }
25014                            }
25015                            self.write(">");
25016                        }
25017                    }
25018                }
25019            }
25020            DataType::Enum {
25021                values,
25022                assignments,
25023            } => {
25024                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
25025                // ClickHouse: Enum('hello' = 1, 'world' = 2)
25026                if self.config.dialect == Some(DialectType::ClickHouse) {
25027                    self.write("Enum(");
25028                } else {
25029                    self.write_keyword("ENUM(");
25030                }
25031                for (i, val) in values.iter().enumerate() {
25032                    if i > 0 {
25033                        self.write(", ");
25034                    }
25035                    self.write("'");
25036                    self.write(val);
25037                    self.write("'");
25038                    if let Some(Some(assignment)) = assignments.get(i) {
25039                        self.write(" = ");
25040                        self.write(assignment);
25041                    }
25042                }
25043                self.write(")");
25044            }
25045            DataType::Set { values } => {
25046                // MySQL SET type: SET('a', 'b', 'c')
25047                self.write_keyword("SET(");
25048                for (i, val) in values.iter().enumerate() {
25049                    if i > 0 {
25050                        self.write(", ");
25051                    }
25052                    self.write("'");
25053                    self.write(val);
25054                    self.write("'");
25055                }
25056                self.write(")");
25057            }
25058            DataType::Union { fields } => {
25059                // DuckDB UNION type: UNION(num INT, str TEXT)
25060                self.write_keyword("UNION(");
25061                for (i, (name, dt)) in fields.iter().enumerate() {
25062                    if i > 0 {
25063                        self.write(", ");
25064                    }
25065                    if !name.is_empty() {
25066                        self.write(name);
25067                        self.write(" ");
25068                    }
25069                    self.generate_data_type(dt)?;
25070                }
25071                self.write(")");
25072            }
25073            DataType::Nullable { inner } => {
25074                // ClickHouse: Nullable(T), other dialects: just the inner type
25075                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25076                    self.write("Nullable(");
25077                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
25078                    let saved_depth = self.clickhouse_nullable_depth;
25079                    self.clickhouse_nullable_depth = -1;
25080                    self.generate_data_type(inner)?;
25081                    self.clickhouse_nullable_depth = saved_depth;
25082                    self.write(")");
25083                } else {
25084                    // Map ClickHouse-specific custom type names to standard types
25085                    match inner.as_ref() {
25086                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
25087                            self.generate_data_type(&DataType::Timestamp {
25088                                precision: None,
25089                                timezone: false,
25090                            })?;
25091                        }
25092                        _ => {
25093                            self.generate_data_type(inner)?;
25094                        }
25095                    }
25096                }
25097            }
25098            DataType::Custom { name } => {
25099                // Handle dialect-specific type transformations
25100                let name_upper = name.to_ascii_uppercase();
25101                match self.config.dialect {
25102                    Some(DialectType::ClickHouse) => {
25103                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
25104                            (name_upper[..idx].to_string(), &name[idx..])
25105                        } else {
25106                            (name_upper.clone(), "")
25107                        };
25108                        let mapped = match base_upper.as_str() {
25109                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
25110                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
25111                            "DATETIME64" => "DateTime64",
25112                            "DATE32" => "Date32",
25113                            "INT" => "Int32",
25114                            "MEDIUMINT" => "Int32",
25115                            "INT8" => "Int8",
25116                            "INT16" => "Int16",
25117                            "INT32" => "Int32",
25118                            "INT64" => "Int64",
25119                            "INT128" => "Int128",
25120                            "INT256" => "Int256",
25121                            "UINT8" => "UInt8",
25122                            "UINT16" => "UInt16",
25123                            "UINT32" => "UInt32",
25124                            "UINT64" => "UInt64",
25125                            "UINT128" => "UInt128",
25126                            "UINT256" => "UInt256",
25127                            "FLOAT32" => "Float32",
25128                            "FLOAT64" => "Float64",
25129                            "DECIMAL32" => "Decimal32",
25130                            "DECIMAL64" => "Decimal64",
25131                            "DECIMAL128" => "Decimal128",
25132                            "DECIMAL256" => "Decimal256",
25133                            "ENUM" => "Enum",
25134                            "ENUM8" => "Enum8",
25135                            "ENUM16" => "Enum16",
25136                            "FIXEDSTRING" => "FixedString",
25137                            "NESTED" => "Nested",
25138                            "LOWCARDINALITY" => "LowCardinality",
25139                            "NULLABLE" => "Nullable",
25140                            "IPV4" => "IPv4",
25141                            "IPV6" => "IPv6",
25142                            "POINT" => "Point",
25143                            "RING" => "Ring",
25144                            "LINESTRING" => "LineString",
25145                            "MULTILINESTRING" => "MultiLineString",
25146                            "POLYGON" => "Polygon",
25147                            "MULTIPOLYGON" => "MultiPolygon",
25148                            "AGGREGATEFUNCTION" => "AggregateFunction",
25149                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
25150                            "DYNAMIC" => "Dynamic",
25151                            _ => "",
25152                        };
25153                        if mapped.is_empty() {
25154                            self.write(name);
25155                        } else {
25156                            self.write(mapped);
25157                            if matches!(base_upper.as_str(), "ENUM8" | "ENUM16")
25158                                && !suffix.is_empty()
25159                            {
25160                                let escaped_suffix = suffix
25161                                    .replace('\\', "\\\\")
25162                                    .replace('\t', "\\t")
25163                                    .replace('\n', "\\n")
25164                                    .replace('\r', "\\r");
25165                                self.write(&escaped_suffix);
25166                            } else {
25167                                self.write(suffix);
25168                            }
25169                        }
25170                    }
25171                    Some(DialectType::MySQL)
25172                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
25173                    {
25174                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
25175                        self.write_keyword("TIMESTAMP");
25176                    }
25177                    Some(DialectType::Snowflake) => {
25178                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
25179                            (name_upper[..idx].to_string(), &name[idx..])
25180                        } else {
25181                            (name_upper.clone(), "")
25182                        };
25183
25184                        match base_upper.as_str() {
25185                            "TIMESTAMPNTZ" | "TIMESTAMP_NTZ" => {
25186                                self.write_keyword("TIMESTAMPNTZ");
25187                                self.write(suffix);
25188                            }
25189                            "TIMESTAMPLTZ" | "TIMESTAMP_LTZ" => {
25190                                self.write_keyword("TIMESTAMPLTZ");
25191                                self.write(suffix);
25192                            }
25193                            "TIMESTAMPTZ" | "TIMESTAMP_TZ" => {
25194                                self.write_keyword("TIMESTAMPTZ");
25195                                self.write(suffix);
25196                            }
25197                            _ => self.write(name),
25198                        }
25199                    }
25200                    Some(DialectType::Fabric) => {
25201                        let (base_upper, args_str) = if let Some(idx) = name.find('(') {
25202                            (name_upper[..idx].to_string(), Some(&name[idx..]))
25203                        } else {
25204                            (name_upper.clone(), None)
25205                        };
25206
25207                        match base_upper.as_str() {
25208                            "NVARCHAR" => {
25209                                self.write_keyword("VARCHAR");
25210                                if let Some(args) = args_str {
25211                                    self.write(args);
25212                                }
25213                            }
25214                            "NCHAR" => {
25215                                self.write_keyword("CHAR");
25216                                if let Some(args) = args_str {
25217                                    self.write(args);
25218                                }
25219                            }
25220                            _ => self.write(name),
25221                        }
25222                    }
25223                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
25224                        self.write_keyword("SQL_VARIANT");
25225                    }
25226                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
25227                        self.write_keyword("DECIMAL(38, 5)");
25228                    }
25229                    Some(DialectType::Exasol) => {
25230                        // Exasol type mappings for custom types
25231                        match name_upper.as_str() {
25232                            // Binary types → VARCHAR
25233                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
25234                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
25235                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
25236                            // Integer types
25237                            "MEDIUMINT" => self.write_keyword("INT"),
25238                            // Decimal types → DECIMAL
25239                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
25240                                self.write_keyword("DECIMAL")
25241                            }
25242                            // Timestamp types
25243                            "DATETIME" => self.write_keyword("TIMESTAMP"),
25244                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
25245                            _ => self.write(name),
25246                        }
25247                    }
25248                    Some(DialectType::Dremio) => {
25249                        // Dremio type mappings for custom types
25250                        match name_upper.as_str() {
25251                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
25252                            "ARRAY" => self.write_keyword("LIST"),
25253                            "NCHAR" => self.write_keyword("VARCHAR"),
25254                            _ => self.write(name),
25255                        }
25256                    }
25257                    // Map dialect-specific custom types to standard SQL types for other dialects
25258                    _ => {
25259                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
25260                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
25261                            (name_upper[..idx].to_string(), Some(&name[idx..]))
25262                        } else {
25263                            (name_upper.clone(), None)
25264                        };
25265
25266                        match base_upper.as_str() {
25267                            "INT64"
25268                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25269                            {
25270                                self.write_keyword("BIGINT");
25271                            }
25272                            "FLOAT64"
25273                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25274                            {
25275                                self.write_keyword("DOUBLE");
25276                            }
25277                            "BOOL"
25278                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25279                            {
25280                                self.write_keyword("BOOLEAN");
25281                            }
25282                            "BYTES"
25283                                if matches!(
25284                                    self.config.dialect,
25285                                    Some(DialectType::Spark)
25286                                        | Some(DialectType::Hive)
25287                                        | Some(DialectType::Databricks)
25288                                ) =>
25289                            {
25290                                self.write_keyword("BINARY");
25291                            }
25292                            "BYTES"
25293                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25294                            {
25295                                self.write_keyword("VARBINARY");
25296                            }
25297                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
25298                            "DATETIME2" | "SMALLDATETIME"
25299                                if !matches!(
25300                                    self.config.dialect,
25301                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25302                                ) =>
25303                            {
25304                                // PostgreSQL preserves precision, others don't
25305                                if matches!(
25306                                    self.config.dialect,
25307                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
25308                                ) {
25309                                    self.write_keyword("TIMESTAMP");
25310                                    if let Some(args) = _args_str {
25311                                        self.write(args);
25312                                    }
25313                                } else {
25314                                    self.write_keyword("TIMESTAMP");
25315                                }
25316                            }
25317                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
25318                            "DATETIMEOFFSET"
25319                                if !matches!(
25320                                    self.config.dialect,
25321                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25322                                ) =>
25323                            {
25324                                if matches!(
25325                                    self.config.dialect,
25326                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
25327                                ) {
25328                                    self.write_keyword("TIMESTAMPTZ");
25329                                    if let Some(args) = _args_str {
25330                                        self.write(args);
25331                                    }
25332                                } else {
25333                                    self.write_keyword("TIMESTAMPTZ");
25334                                }
25335                            }
25336                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
25337                            "UNIQUEIDENTIFIER"
25338                                if !matches!(
25339                                    self.config.dialect,
25340                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25341                                ) =>
25342                            {
25343                                match self.config.dialect {
25344                                    Some(DialectType::Spark)
25345                                    | Some(DialectType::Databricks)
25346                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
25347                                    _ => self.write_keyword("UUID"),
25348                                }
25349                            }
25350                            // TSQL BIT -> BOOLEAN for most dialects
25351                            "BIT"
25352                                if !matches!(
25353                                    self.config.dialect,
25354                                    Some(DialectType::TSQL)
25355                                        | Some(DialectType::Fabric)
25356                                        | Some(DialectType::PostgreSQL)
25357                                        | Some(DialectType::MySQL)
25358                                        | Some(DialectType::DuckDB)
25359                                ) =>
25360                            {
25361                                self.write_keyword("BOOLEAN");
25362                            }
25363                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
25364                            "NVARCHAR"
25365                                if !matches!(
25366                                    self.config.dialect,
25367                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25368                                ) =>
25369                            {
25370                                match self.config.dialect {
25371                                    Some(DialectType::Oracle) => {
25372                                        // Oracle: NVARCHAR -> NVARCHAR2
25373                                        self.write_keyword("NVARCHAR2");
25374                                        if let Some(args) = _args_str {
25375                                            self.write(args);
25376                                        }
25377                                    }
25378                                    Some(DialectType::BigQuery) => {
25379                                        // BigQuery: NVARCHAR -> STRING
25380                                        self.write_keyword("STRING");
25381                                    }
25382                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
25383                                        self.write_keyword("TEXT");
25384                                        if let Some(args) = _args_str {
25385                                            self.write(args);
25386                                        }
25387                                    }
25388                                    Some(DialectType::Hive) => {
25389                                        // Hive: NVARCHAR -> STRING
25390                                        self.write_keyword("STRING");
25391                                    }
25392                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
25393                                        if _args_str.is_some() {
25394                                            self.write_keyword("VARCHAR");
25395                                            self.write(_args_str.unwrap());
25396                                        } else {
25397                                            self.write_keyword("STRING");
25398                                        }
25399                                    }
25400                                    _ => {
25401                                        self.write_keyword("VARCHAR");
25402                                        if let Some(args) = _args_str {
25403                                            self.write(args);
25404                                        }
25405                                    }
25406                                }
25407                            }
25408                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
25409                            "NCHAR"
25410                                if !matches!(
25411                                    self.config.dialect,
25412                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25413                                ) =>
25414                            {
25415                                match self.config.dialect {
25416                                    Some(DialectType::Oracle) => {
25417                                        // Oracle natively supports NCHAR
25418                                        self.write_keyword("NCHAR");
25419                                        if let Some(args) = _args_str {
25420                                            self.write(args);
25421                                        }
25422                                    }
25423                                    Some(DialectType::BigQuery) => {
25424                                        // BigQuery: NCHAR -> STRING
25425                                        self.write_keyword("STRING");
25426                                    }
25427                                    Some(DialectType::Hive) => {
25428                                        // Hive: NCHAR -> STRING
25429                                        self.write_keyword("STRING");
25430                                    }
25431                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
25432                                        self.write_keyword("TEXT");
25433                                        if let Some(args) = _args_str {
25434                                            self.write(args);
25435                                        }
25436                                    }
25437                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
25438                                        if _args_str.is_some() {
25439                                            self.write_keyword("CHAR");
25440                                            self.write(_args_str.unwrap());
25441                                        } else {
25442                                            self.write_keyword("STRING");
25443                                        }
25444                                    }
25445                                    _ => {
25446                                        self.write_keyword("CHAR");
25447                                        if let Some(args) = _args_str {
25448                                            self.write(args);
25449                                        }
25450                                    }
25451                                }
25452                            }
25453                            // MySQL text variant types -> map to appropriate target type
25454                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
25455                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
25456                                Some(DialectType::MySQL)
25457                                | Some(DialectType::SingleStore)
25458                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
25459                                Some(DialectType::Spark)
25460                                | Some(DialectType::Databricks)
25461                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
25462                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
25463                                Some(DialectType::Presto)
25464                                | Some(DialectType::Trino)
25465                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
25466                                Some(DialectType::Snowflake)
25467                                | Some(DialectType::Redshift)
25468                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
25469                                _ => self.write_keyword("TEXT"),
25470                            },
25471                            // MySQL blob variant types -> map to appropriate target type
25472                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
25473                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
25474                                Some(DialectType::MySQL)
25475                                | Some(DialectType::SingleStore)
25476                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
25477                                Some(DialectType::Spark)
25478                                | Some(DialectType::Databricks)
25479                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
25480                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
25481                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
25482                                Some(DialectType::Presto)
25483                                | Some(DialectType::Trino)
25484                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
25485                                Some(DialectType::Snowflake)
25486                                | Some(DialectType::Redshift)
25487                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
25488                                _ => self.write_keyword("BLOB"),
25489                            },
25490                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
25491                            "LONGVARCHAR" => match self.config.dialect {
25492                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
25493                                _ => self.write_keyword("VARCHAR"),
25494                            },
25495                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
25496                            "DATETIME" => {
25497                                match self.config.dialect {
25498                                    Some(DialectType::MySQL)
25499                                    | Some(DialectType::Doris)
25500                                    | Some(DialectType::StarRocks)
25501                                    | Some(DialectType::TSQL)
25502                                    | Some(DialectType::Fabric)
25503                                    | Some(DialectType::BigQuery)
25504                                    | Some(DialectType::SQLite)
25505                                    | Some(DialectType::Snowflake) => {
25506                                        self.write_keyword("DATETIME");
25507                                        if let Some(args) = _args_str {
25508                                            self.write(args);
25509                                        }
25510                                    }
25511                                    Some(_) => {
25512                                        // Only map to TIMESTAMP when we have a specific target dialect
25513                                        self.write_keyword("TIMESTAMP");
25514                                        if let Some(args) = _args_str {
25515                                            self.write(args);
25516                                        }
25517                                    }
25518                                    None => {
25519                                        // No dialect - preserve original
25520                                        self.write(name);
25521                                    }
25522                                }
25523                            }
25524                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
25525                            "VARCHAR2"
25526                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
25527                            {
25528                                match self.config.dialect {
25529                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
25530                                        self.write_keyword("TEXT");
25531                                    }
25532                                    Some(DialectType::Hive)
25533                                    | Some(DialectType::Spark)
25534                                    | Some(DialectType::Databricks)
25535                                    | Some(DialectType::BigQuery)
25536                                    | Some(DialectType::ClickHouse)
25537                                    | Some(DialectType::StarRocks)
25538                                    | Some(DialectType::Doris) => {
25539                                        self.write_keyword("STRING");
25540                                    }
25541                                    _ => {
25542                                        self.write_keyword("VARCHAR");
25543                                        if let Some(args) = _args_str {
25544                                            self.write(args);
25545                                        }
25546                                    }
25547                                }
25548                            }
25549                            "NVARCHAR2"
25550                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
25551                            {
25552                                match self.config.dialect {
25553                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
25554                                        self.write_keyword("TEXT");
25555                                    }
25556                                    Some(DialectType::Hive)
25557                                    | Some(DialectType::Spark)
25558                                    | Some(DialectType::Databricks)
25559                                    | Some(DialectType::BigQuery)
25560                                    | Some(DialectType::ClickHouse)
25561                                    | Some(DialectType::StarRocks)
25562                                    | Some(DialectType::Doris) => {
25563                                        self.write_keyword("STRING");
25564                                    }
25565                                    _ => {
25566                                        self.write_keyword("VARCHAR");
25567                                        if let Some(args) = _args_str {
25568                                            self.write(args);
25569                                        }
25570                                    }
25571                                }
25572                            }
25573                            _ => self.write(name),
25574                        }
25575                    }
25576                }
25577            }
25578            DataType::Geometry { subtype, srid } => {
25579                // Dialect-specific geometry type mappings
25580                match self.config.dialect {
25581                    Some(DialectType::MySQL) => {
25582                        // MySQL uses POINT SRID 4326 syntax for specific types
25583                        if let Some(sub) = subtype {
25584                            self.write_keyword(sub);
25585                            if let Some(s) = srid {
25586                                self.write(" SRID ");
25587                                self.write(&s.to_string());
25588                            }
25589                        } else {
25590                            self.write_keyword("GEOMETRY");
25591                        }
25592                    }
25593                    Some(DialectType::BigQuery) => {
25594                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
25595                        self.write_keyword("GEOGRAPHY");
25596                    }
25597                    Some(DialectType::Teradata) => {
25598                        // Teradata uses ST_GEOMETRY
25599                        self.write_keyword("ST_GEOMETRY");
25600                        if subtype.is_some() || srid.is_some() {
25601                            self.write("(");
25602                            if let Some(sub) = subtype {
25603                                self.write_keyword(sub);
25604                            }
25605                            if let Some(s) = srid {
25606                                if subtype.is_some() {
25607                                    self.write(", ");
25608                                }
25609                                self.write(&s.to_string());
25610                            }
25611                            self.write(")");
25612                        }
25613                    }
25614                    _ => {
25615                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
25616                        self.write_keyword("GEOMETRY");
25617                        if subtype.is_some() || srid.is_some() {
25618                            self.write("(");
25619                            if let Some(sub) = subtype {
25620                                self.write_keyword(sub);
25621                            }
25622                            if let Some(s) = srid {
25623                                if subtype.is_some() {
25624                                    self.write(", ");
25625                                }
25626                                self.write(&s.to_string());
25627                            }
25628                            self.write(")");
25629                        }
25630                    }
25631                }
25632            }
25633            DataType::Geography { subtype, srid } => {
25634                // Dialect-specific geography type mappings
25635                match self.config.dialect {
25636                    Some(DialectType::MySQL) => {
25637                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
25638                        if let Some(sub) = subtype {
25639                            self.write_keyword(sub);
25640                        } else {
25641                            self.write_keyword("GEOMETRY");
25642                        }
25643                        // Geography implies SRID 4326 (WGS84)
25644                        let effective_srid = srid.unwrap_or(4326);
25645                        self.write(" SRID ");
25646                        self.write(&effective_srid.to_string());
25647                    }
25648                    Some(DialectType::BigQuery) => {
25649                        // BigQuery uses simple GEOGRAPHY without parameters
25650                        self.write_keyword("GEOGRAPHY");
25651                    }
25652                    Some(DialectType::Snowflake) => {
25653                        // Snowflake uses GEOGRAPHY without parameters
25654                        self.write_keyword("GEOGRAPHY");
25655                    }
25656                    _ => {
25657                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
25658                        self.write_keyword("GEOGRAPHY");
25659                        if subtype.is_some() || srid.is_some() {
25660                            self.write("(");
25661                            if let Some(sub) = subtype {
25662                                self.write_keyword(sub);
25663                            }
25664                            if let Some(s) = srid {
25665                                if subtype.is_some() {
25666                                    self.write(", ");
25667                                }
25668                                self.write(&s.to_string());
25669                            }
25670                            self.write(")");
25671                        }
25672                    }
25673                }
25674            }
25675            DataType::CharacterSet { name } => {
25676                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
25677                self.write_keyword("CHAR CHARACTER SET ");
25678                self.write(name);
25679            }
25680            _ => self.write("UNKNOWN"),
25681        }
25682        Ok(())
25683    }
25684
25685    // === Helper methods ===
25686
25687    #[inline]
25688    fn write(&mut self, s: &str) {
25689        self.output.push_str(s);
25690    }
25691
25692    #[inline]
25693    fn write_space(&mut self) {
25694        self.output.push(' ');
25695    }
25696
25697    #[inline]
25698    fn write_keyword(&mut self, keyword: &str) {
25699        if self.config.uppercase_keywords {
25700            self.output.push_str(keyword);
25701        } else {
25702            for b in keyword.bytes() {
25703                self.output.push(b.to_ascii_lowercase() as char);
25704            }
25705        }
25706    }
25707
25708    /// Write a function name respecting the normalize_functions config setting
25709    fn write_func_name(&mut self, name: &str) {
25710        let normalized = self.normalize_func_name(name);
25711        self.output.push_str(normalized.as_ref());
25712    }
25713
25714    /// Convert strptime format string to Exasol format string
25715    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
25716    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
25717    fn convert_strptime_to_exasol_format(format: &str) -> String {
25718        let mut result = String::new();
25719        let chars: Vec<char> = format.chars().collect();
25720        let mut i = 0;
25721        while i < chars.len() {
25722            if chars[i] == '%' && i + 1 < chars.len() {
25723                let spec = chars[i + 1];
25724                let exasol_spec = match spec {
25725                    'Y' => "YYYY",
25726                    'y' => "YY",
25727                    'm' => "MM",
25728                    'd' => "DD",
25729                    'H' => "HH",
25730                    'M' => "MI",
25731                    'S' => "SS",
25732                    'a' => "DY",    // abbreviated weekday name
25733                    'A' => "DAY",   // full weekday name
25734                    'b' => "MON",   // abbreviated month name
25735                    'B' => "MONTH", // full month name
25736                    'I' => "H12",   // 12-hour format
25737                    'u' => "ID",    // ISO weekday (1-7)
25738                    'V' => "IW",    // ISO week number
25739                    'G' => "IYYY",  // ISO year
25740                    'W' => "UW",    // Week number (Monday as first day)
25741                    'U' => "UW",    // Week number (Sunday as first day)
25742                    'z' => "Z",     // timezone offset
25743                    _ => {
25744                        // Unknown specifier, keep as-is
25745                        result.push('%');
25746                        result.push(spec);
25747                        i += 2;
25748                        continue;
25749                    }
25750                };
25751                result.push_str(exasol_spec);
25752                i += 2;
25753            } else {
25754                result.push(chars[i]);
25755                i += 1;
25756            }
25757        }
25758        result
25759    }
25760
25761    /// Convert strptime format string to PostgreSQL/Redshift format string
25762    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
25763    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
25764    fn convert_strptime_to_postgres_format(format: &str) -> String {
25765        let mut result = String::new();
25766        let chars: Vec<char> = format.chars().collect();
25767        let mut i = 0;
25768        while i < chars.len() {
25769            if chars[i] == '%' && i + 1 < chars.len() {
25770                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
25771                if chars[i + 1] == '-' && i + 2 < chars.len() {
25772                    let spec = chars[i + 2];
25773                    let pg_spec = match spec {
25774                        'd' => "FMDD",
25775                        'm' => "FMMM",
25776                        'H' => "FMHH24",
25777                        'M' => "FMMI",
25778                        'S' => "FMSS",
25779                        _ => {
25780                            result.push('%');
25781                            result.push('-');
25782                            result.push(spec);
25783                            i += 3;
25784                            continue;
25785                        }
25786                    };
25787                    result.push_str(pg_spec);
25788                    i += 3;
25789                    continue;
25790                }
25791                let spec = chars[i + 1];
25792                let pg_spec = match spec {
25793                    'Y' => "YYYY",
25794                    'y' => "YY",
25795                    'm' => "MM",
25796                    'd' => "DD",
25797                    'H' => "HH24",
25798                    'I' => "HH12",
25799                    'M' => "MI",
25800                    'S' => "SS",
25801                    'f' => "US",      // microseconds
25802                    'u' => "D",       // day of week (1=Monday)
25803                    'j' => "DDD",     // day of year
25804                    'z' => "OF",      // UTC offset
25805                    'Z' => "TZ",      // timezone name
25806                    'A' => "TMDay",   // full weekday name
25807                    'a' => "TMDy",    // abbreviated weekday name
25808                    'b' => "TMMon",   // abbreviated month name
25809                    'B' => "TMMonth", // full month name
25810                    'U' => "WW",      // week number
25811                    _ => {
25812                        // Unknown specifier, keep as-is
25813                        result.push('%');
25814                        result.push(spec);
25815                        i += 2;
25816                        continue;
25817                    }
25818                };
25819                result.push_str(pg_spec);
25820                i += 2;
25821            } else {
25822                result.push(chars[i]);
25823                i += 1;
25824            }
25825        }
25826        result
25827    }
25828
25829    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
25830    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
25831        if self.config.limit_only_literals {
25832            if let Some(value) = Self::try_evaluate_constant(expr) {
25833                self.write(&value.to_string());
25834                return Ok(());
25835            }
25836        }
25837        self.generate_expression(expr)
25838    }
25839
25840    /// Format a comment with proper spacing.
25841    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
25842    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
25843    fn write_formatted_comment(&mut self, comment: &str) {
25844        // Normalize all comments to block comment format /* ... */
25845        // This matches Python sqlglot behavior which always outputs block comments
25846        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
25847            // Already block comment - extract inner content
25848            // Preserve internal whitespace, but ensure at least one space padding
25849            &comment[2..comment.len() - 2]
25850        } else if comment.starts_with("--") {
25851            // Line comment - extract content after --
25852            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
25853            &comment[2..]
25854        } else {
25855            // Raw content (no delimiters)
25856            comment
25857        };
25858        // Skip empty comments (e.g., bare "--" with no content)
25859        if content.trim().is_empty() {
25860            return;
25861        }
25862        // Escape nested block comment markers to prevent premature closure or unintended nesting.
25863        // This matches Python sqlglot's sanitize_comment behavior.
25864        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
25865        let content = &sanitized;
25866        // Ensure at least one space after /* and before */
25867        self.output.push_str("/*");
25868        if !content.starts_with(' ') {
25869            self.output.push(' ');
25870        }
25871        self.output.push_str(content);
25872        if !content.ends_with(' ') {
25873            self.output.push(' ');
25874        }
25875        self.output.push_str("*/");
25876    }
25877
25878    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
25879    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
25880    fn escape_block_for_single_quote(&self, block: &str) -> String {
25881        let escape_backslash = matches!(
25882            self.config.dialect,
25883            Some(crate::dialects::DialectType::Snowflake)
25884        );
25885        let mut escaped = String::with_capacity(block.len() + 4);
25886        for ch in block.chars() {
25887            if ch == '\'' {
25888                escaped.push('\\');
25889                escaped.push('\'');
25890            } else if escape_backslash && ch == '\\' {
25891                escaped.push('\\');
25892                escaped.push('\\');
25893            } else {
25894                escaped.push(ch);
25895            }
25896        }
25897        escaped
25898    }
25899
25900    fn write_newline(&mut self) {
25901        self.output.push('\n');
25902    }
25903
25904    fn write_indent(&mut self) {
25905        for _ in 0..self.indent_level {
25906            self.output.push_str(self.config.indent);
25907        }
25908    }
25909
25910    // === SQLGlot-style pretty printing helpers ===
25911
25912    /// Returns the separator string for pretty printing.
25913    /// Check if the total length of arguments exceeds max_text_width.
25914    /// Used for dynamic line breaking in expressions() formatting.
25915    fn too_wide(&self, args: &[String]) -> bool {
25916        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
25917    }
25918
25919    /// Generate an expression to a string using a temporary non-pretty generator.
25920    /// Useful for width calculations before deciding on formatting.
25921    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
25922        let config = GeneratorConfig {
25923            pretty: false,
25924            dialect: self.config.dialect,
25925            ..Default::default()
25926        };
25927        let mut gen = Generator::with_config(config);
25928        gen.generate_expression(expr)?;
25929        Ok(gen.output)
25930    }
25931
25932    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
25933    /// In pretty mode: newline + indented keyword + newline + indented condition
25934    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
25935        if self.config.pretty {
25936            self.write_newline();
25937            self.write_indent();
25938            self.write_keyword(keyword);
25939            self.write_newline();
25940            self.indent_level += 1;
25941            self.write_indent();
25942            self.generate_expression(condition)?;
25943            self.indent_level -= 1;
25944        } else {
25945            self.write_space();
25946            self.write_keyword(keyword);
25947            self.write_space();
25948            self.generate_expression(condition)?;
25949        }
25950        Ok(())
25951    }
25952
25953    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
25954    /// In pretty mode: each expression on new line with indentation
25955    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
25956        if exprs.is_empty() {
25957            return Ok(());
25958        }
25959
25960        if self.config.pretty {
25961            self.write_newline();
25962            self.write_indent();
25963            self.write_keyword(keyword);
25964            self.write_newline();
25965            self.indent_level += 1;
25966            for (i, expr) in exprs.iter().enumerate() {
25967                if i > 0 {
25968                    self.write(",");
25969                    self.write_newline();
25970                }
25971                self.write_indent();
25972                self.generate_expression(expr)?;
25973            }
25974            self.indent_level -= 1;
25975        } else {
25976            self.write_space();
25977            self.write_keyword(keyword);
25978            self.write_space();
25979            for (i, expr) in exprs.iter().enumerate() {
25980                if i > 0 {
25981                    self.write(", ");
25982                }
25983                self.generate_expression(expr)?;
25984            }
25985        }
25986        Ok(())
25987    }
25988
25989    /// Writes ORDER BY / SORT BY clause with Ordered expressions
25990    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
25991        if orderings.is_empty() {
25992            return Ok(());
25993        }
25994
25995        if self.config.pretty {
25996            self.write_newline();
25997            self.write_indent();
25998            self.write_keyword(keyword);
25999            self.write_newline();
26000            self.indent_level += 1;
26001            for (i, ordered) in orderings.iter().enumerate() {
26002                if i > 0 {
26003                    self.write(",");
26004                    self.write_newline();
26005                }
26006                self.write_indent();
26007                self.generate_ordered(ordered)?;
26008            }
26009            self.indent_level -= 1;
26010        } else {
26011            self.write_space();
26012            self.write_keyword(keyword);
26013            self.write_space();
26014            for (i, ordered) in orderings.iter().enumerate() {
26015                if i > 0 {
26016                    self.write(", ");
26017                }
26018                self.generate_ordered(ordered)?;
26019            }
26020        }
26021        Ok(())
26022    }
26023
26024    /// Writes WINDOW clause with named window definitions
26025    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
26026        if windows.is_empty() {
26027            return Ok(());
26028        }
26029
26030        if self.config.pretty {
26031            self.write_newline();
26032            self.write_indent();
26033            self.write_keyword("WINDOW");
26034            self.write_newline();
26035            self.indent_level += 1;
26036            for (i, named_window) in windows.iter().enumerate() {
26037                if i > 0 {
26038                    self.write(",");
26039                    self.write_newline();
26040                }
26041                self.write_indent();
26042                self.generate_identifier(&named_window.name)?;
26043                self.write_space();
26044                self.write_keyword("AS");
26045                self.write(" (");
26046                self.generate_over(&named_window.spec)?;
26047                self.write(")");
26048            }
26049            self.indent_level -= 1;
26050        } else {
26051            self.write_space();
26052            self.write_keyword("WINDOW");
26053            self.write_space();
26054            for (i, named_window) in windows.iter().enumerate() {
26055                if i > 0 {
26056                    self.write(", ");
26057                }
26058                self.generate_identifier(&named_window.name)?;
26059                self.write_space();
26060                self.write_keyword("AS");
26061                self.write(" (");
26062                self.generate_over(&named_window.spec)?;
26063                self.write(")");
26064            }
26065        }
26066        Ok(())
26067    }
26068
26069    // === BATCH-GENERATED STUB METHODS (481 variants) ===
26070    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
26071        // AI_AGG(this, expression)
26072        self.write_keyword("AI_AGG");
26073        self.write("(");
26074        self.generate_expression(&e.this)?;
26075        self.write(", ");
26076        self.generate_expression(&e.expression)?;
26077        self.write(")");
26078        Ok(())
26079    }
26080
26081    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
26082        // AI_CLASSIFY(input, [categories], [config])
26083        self.write_keyword("AI_CLASSIFY");
26084        self.write("(");
26085        self.generate_expression(&e.this)?;
26086        if let Some(categories) = &e.categories {
26087            self.write(", ");
26088            self.generate_expression(categories)?;
26089        }
26090        if let Some(config) = &e.config {
26091            self.write(", ");
26092            self.generate_expression(config)?;
26093        }
26094        self.write(")");
26095        Ok(())
26096    }
26097
26098    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
26099        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
26100        self.write_keyword("ADD");
26101        self.write_space();
26102        if e.exists {
26103            self.write_keyword("IF NOT EXISTS");
26104            self.write_space();
26105        }
26106        self.generate_expression(&e.this)?;
26107        if let Some(location) = &e.location {
26108            self.write_space();
26109            self.generate_expression(location)?;
26110        }
26111        Ok(())
26112    }
26113
26114    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
26115        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
26116        self.write_keyword("ALGORITHM");
26117        self.write("=");
26118        self.generate_expression(&e.this)?;
26119        Ok(())
26120    }
26121
26122    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
26123        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
26124        self.generate_expression(&e.this)?;
26125        self.write_space();
26126        self.write_keyword("AS");
26127        self.write(" (");
26128        for (i, expr) in e.expressions.iter().enumerate() {
26129            if i > 0 {
26130                self.write(", ");
26131            }
26132            self.generate_expression(expr)?;
26133        }
26134        self.write(")");
26135        Ok(())
26136    }
26137
26138    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
26139        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
26140        self.write_keyword("ALLOWED_VALUES");
26141        self.write_space();
26142        for (i, expr) in e.expressions.iter().enumerate() {
26143            if i > 0 {
26144                self.write(", ");
26145            }
26146            self.generate_expression(expr)?;
26147        }
26148        Ok(())
26149    }
26150
26151    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
26152        // Python: complex logic based on dtype, default, comment, visible, etc.
26153        self.write_keyword("ALTER COLUMN");
26154        self.write_space();
26155        self.generate_expression(&e.this)?;
26156
26157        if let Some(dtype) = &e.dtype {
26158            self.write_space();
26159            self.write_keyword("SET DATA TYPE");
26160            self.write_space();
26161            self.generate_expression(dtype)?;
26162            if let Some(collate) = &e.collate {
26163                self.write_space();
26164                self.write_keyword("COLLATE");
26165                self.write_space();
26166                self.generate_expression(collate)?;
26167            }
26168            if let Some(using) = &e.using {
26169                self.write_space();
26170                self.write_keyword("USING");
26171                self.write_space();
26172                self.generate_expression(using)?;
26173            }
26174        } else if let Some(default) = &e.default {
26175            self.write_space();
26176            self.write_keyword("SET DEFAULT");
26177            self.write_space();
26178            self.generate_expression(default)?;
26179        } else if let Some(comment) = &e.comment {
26180            self.write_space();
26181            self.write_keyword("COMMENT");
26182            self.write_space();
26183            self.generate_expression(comment)?;
26184        } else if let Some(drop) = &e.drop {
26185            self.write_space();
26186            self.write_keyword("DROP");
26187            self.write_space();
26188            self.generate_expression(drop)?;
26189        } else if let Some(visible) = &e.visible {
26190            self.write_space();
26191            self.generate_expression(visible)?;
26192        } else if let Some(rename_to) = &e.rename_to {
26193            self.write_space();
26194            self.write_keyword("RENAME TO");
26195            self.write_space();
26196            self.generate_expression(rename_to)?;
26197        } else if let Some(allow_null) = &e.allow_null {
26198            self.write_space();
26199            self.generate_expression(allow_null)?;
26200        }
26201        Ok(())
26202    }
26203
26204    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
26205        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
26206        self.write_keyword("ALTER SESSION");
26207        self.write_space();
26208        if e.unset.is_some() {
26209            self.write_keyword("UNSET");
26210        } else {
26211            self.write_keyword("SET");
26212        }
26213        self.write_space();
26214        for (i, expr) in e.expressions.iter().enumerate() {
26215            if i > 0 {
26216                self.write(", ");
26217            }
26218            self.generate_expression(expr)?;
26219        }
26220        Ok(())
26221    }
26222
26223    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
26224        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
26225        self.write_keyword("SET");
26226
26227        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
26228        if let Some(opt) = &e.option {
26229            self.write_space();
26230            self.generate_expression(opt)?;
26231        }
26232
26233        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
26234        // Check if expressions look like property assignments
26235        if !e.expressions.is_empty() {
26236            // Check if this looks like property assignments (for SET PROPERTIES)
26237            let is_properties = e
26238                .expressions
26239                .iter()
26240                .any(|expr| matches!(expr, Expression::Eq(_)));
26241            if is_properties && e.option.is_none() {
26242                self.write_space();
26243                self.write_keyword("PROPERTIES");
26244            }
26245            self.write_space();
26246            for (i, expr) in e.expressions.iter().enumerate() {
26247                if i > 0 {
26248                    self.write(", ");
26249                }
26250                self.generate_expression(expr)?;
26251            }
26252        }
26253
26254        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
26255        if let Some(file_format) = &e.file_format {
26256            self.write(" ");
26257            self.write_keyword("STAGE_FILE_FORMAT");
26258            self.write(" = (");
26259            self.generate_space_separated_properties(file_format)?;
26260            self.write(")");
26261        }
26262
26263        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
26264        if let Some(copy_options) = &e.copy_options {
26265            self.write(" ");
26266            self.write_keyword("STAGE_COPY_OPTIONS");
26267            self.write(" = (");
26268            self.generate_space_separated_properties(copy_options)?;
26269            self.write(")");
26270        }
26271
26272        // Generate TAG ...
26273        if let Some(tag) = &e.tag {
26274            self.write(" ");
26275            self.write_keyword("TAG");
26276            self.write(" ");
26277            self.generate_expression(tag)?;
26278        }
26279
26280        Ok(())
26281    }
26282
26283    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
26284    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
26285        match expr {
26286            Expression::Tuple(t) => {
26287                for (i, prop) in t.expressions.iter().enumerate() {
26288                    if i > 0 {
26289                        self.write(" ");
26290                    }
26291                    self.generate_expression(prop)?;
26292                }
26293            }
26294            _ => {
26295                self.generate_expression(expr)?;
26296            }
26297        }
26298        Ok(())
26299    }
26300
26301    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
26302        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
26303        self.write_keyword("ALTER");
26304        if e.compound.is_some() {
26305            self.write_space();
26306            self.write_keyword("COMPOUND");
26307        }
26308        self.write_space();
26309        self.write_keyword("SORTKEY");
26310        self.write_space();
26311        if let Some(this) = &e.this {
26312            self.generate_expression(this)?;
26313        } else if !e.expressions.is_empty() {
26314            self.write("(");
26315            for (i, expr) in e.expressions.iter().enumerate() {
26316                if i > 0 {
26317                    self.write(", ");
26318                }
26319                self.generate_expression(expr)?;
26320            }
26321            self.write(")");
26322        }
26323        Ok(())
26324    }
26325
26326    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
26327        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
26328        self.write_keyword("ANALYZE");
26329        if !e.options.is_empty() {
26330            self.write_space();
26331            for (i, opt) in e.options.iter().enumerate() {
26332                if i > 0 {
26333                    self.write_space();
26334                }
26335                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
26336                if let Expression::Identifier(id) = opt {
26337                    self.write_keyword(&id.name);
26338                } else {
26339                    self.generate_expression(opt)?;
26340                }
26341            }
26342        }
26343        if let Some(kind) = &e.kind {
26344            self.write_space();
26345            self.write_keyword(kind);
26346        }
26347        if let Some(this) = &e.this {
26348            self.write_space();
26349            self.generate_expression(this)?;
26350        }
26351        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
26352        if !e.columns.is_empty() {
26353            self.write("(");
26354            for (i, col) in e.columns.iter().enumerate() {
26355                if i > 0 {
26356                    self.write(", ");
26357                }
26358                self.write(col);
26359            }
26360            self.write(")");
26361        }
26362        if let Some(partition) = &e.partition {
26363            self.write_space();
26364            self.generate_expression(partition)?;
26365        }
26366        if let Some(mode) = &e.mode {
26367            self.write_space();
26368            self.generate_expression(mode)?;
26369        }
26370        if let Some(expression) = &e.expression {
26371            self.write_space();
26372            self.generate_expression(expression)?;
26373        }
26374        if !e.properties.is_empty() {
26375            self.write_space();
26376            self.write_keyword(self.config.with_properties_prefix);
26377            self.write(" (");
26378            for (i, prop) in e.properties.iter().enumerate() {
26379                if i > 0 {
26380                    self.write(", ");
26381                }
26382                self.generate_expression(prop)?;
26383            }
26384            self.write(")");
26385        }
26386        Ok(())
26387    }
26388
26389    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
26390        // Python: return f"DELETE{kind} STATISTICS"
26391        self.write_keyword("DELETE");
26392        if let Some(kind) = &e.kind {
26393            self.write_space();
26394            self.write_keyword(kind);
26395        }
26396        self.write_space();
26397        self.write_keyword("STATISTICS");
26398        Ok(())
26399    }
26400
26401    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
26402        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
26403        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
26404        if let Expression::Identifier(id) = e.this.as_ref() {
26405            self.write_keyword(&id.name);
26406        } else {
26407            self.generate_expression(&e.this)?;
26408        }
26409        self.write_space();
26410        self.write_keyword("HISTOGRAM ON");
26411        self.write_space();
26412        for (i, expr) in e.expressions.iter().enumerate() {
26413            if i > 0 {
26414                self.write(", ");
26415            }
26416            self.generate_expression(expr)?;
26417        }
26418        if let Some(expression) = &e.expression {
26419            self.write_space();
26420            self.generate_expression(expression)?;
26421        }
26422        if let Some(update_options) = &e.update_options {
26423            self.write_space();
26424            self.generate_expression(update_options)?;
26425            self.write_space();
26426            self.write_keyword("UPDATE");
26427        }
26428        Ok(())
26429    }
26430
26431    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
26432        // Python: return f"LIST CHAINED ROWS{inner_expression}"
26433        self.write_keyword("LIST CHAINED ROWS");
26434        if let Some(expression) = &e.expression {
26435            self.write_space();
26436            self.write_keyword("INTO");
26437            self.write_space();
26438            self.generate_expression(expression)?;
26439        }
26440        Ok(())
26441    }
26442
26443    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
26444        // Python: return f"SAMPLE {sample} {kind}"
26445        self.write_keyword("SAMPLE");
26446        self.write_space();
26447        if let Some(sample) = &e.sample {
26448            self.generate_expression(sample)?;
26449            self.write_space();
26450        }
26451        self.write_keyword(&e.kind);
26452        Ok(())
26453    }
26454
26455    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
26456        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
26457        self.write_keyword(&e.kind);
26458        if let Some(option) = &e.option {
26459            self.write_space();
26460            self.generate_expression(option)?;
26461        }
26462        self.write_space();
26463        self.write_keyword("STATISTICS");
26464        if let Some(this) = &e.this {
26465            self.write_space();
26466            self.generate_expression(this)?;
26467        }
26468        if !e.expressions.is_empty() {
26469            self.write_space();
26470            for (i, expr) in e.expressions.iter().enumerate() {
26471                if i > 0 {
26472                    self.write(", ");
26473                }
26474                self.generate_expression(expr)?;
26475            }
26476        }
26477        Ok(())
26478    }
26479
26480    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
26481        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
26482        self.write_keyword("VALIDATE");
26483        self.write_space();
26484        self.write_keyword(&e.kind);
26485        if let Some(this) = &e.this {
26486            self.write_space();
26487            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
26488            if let Expression::Identifier(id) = this.as_ref() {
26489                self.write_keyword(&id.name);
26490            } else {
26491                self.generate_expression(this)?;
26492            }
26493        }
26494        if let Some(expression) = &e.expression {
26495            self.write_space();
26496            self.write_keyword("INTO");
26497            self.write_space();
26498            self.generate_expression(expression)?;
26499        }
26500        Ok(())
26501    }
26502
26503    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
26504        // Python: return f"WITH {expressions}"
26505        self.write_keyword("WITH");
26506        self.write_space();
26507        for (i, expr) in e.expressions.iter().enumerate() {
26508            if i > 0 {
26509                self.write(", ");
26510            }
26511            self.generate_expression(expr)?;
26512        }
26513        Ok(())
26514    }
26515
26516    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
26517        // Anonymous represents a generic function call: FUNC_NAME(args...)
26518        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
26519        self.generate_expression(&e.this)?;
26520        self.write("(");
26521        for (i, arg) in e.expressions.iter().enumerate() {
26522            if i > 0 {
26523                self.write(", ");
26524            }
26525            self.generate_expression(arg)?;
26526        }
26527        self.write(")");
26528        Ok(())
26529    }
26530
26531    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
26532        // Same as Anonymous but for aggregate functions
26533        self.generate_expression(&e.this)?;
26534        self.write("(");
26535        for (i, arg) in e.expressions.iter().enumerate() {
26536            if i > 0 {
26537                self.write(", ");
26538            }
26539            self.generate_expression(arg)?;
26540        }
26541        self.write(")");
26542        Ok(())
26543    }
26544
26545    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
26546        // Python: return f"{this} APPLY({expr})"
26547        self.generate_expression(&e.this)?;
26548        self.write_space();
26549        self.write_keyword("APPLY");
26550        self.write("(");
26551        self.generate_expression(&e.expression)?;
26552        self.write(")");
26553        Ok(())
26554    }
26555
26556    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
26557        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
26558        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
26559        self.write("(");
26560        self.generate_expression(&e.this)?;
26561        if let Some(percentile) = &e.percentile {
26562            self.write(", ");
26563            self.generate_expression(percentile)?;
26564        }
26565        self.write(")");
26566        Ok(())
26567    }
26568
26569    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
26570        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
26571        self.write_keyword("APPROX_QUANTILE");
26572        self.write("(");
26573        self.generate_expression(&e.this)?;
26574        if let Some(quantile) = &e.quantile {
26575            self.write(", ");
26576            self.generate_expression(quantile)?;
26577        }
26578        if let Some(accuracy) = &e.accuracy {
26579            self.write(", ");
26580            self.generate_expression(accuracy)?;
26581        }
26582        if let Some(weight) = &e.weight {
26583            self.write(", ");
26584            self.generate_expression(weight)?;
26585        }
26586        self.write(")");
26587        Ok(())
26588    }
26589
26590    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
26591        // APPROX_QUANTILES(this, expression)
26592        self.write_keyword("APPROX_QUANTILES");
26593        self.write("(");
26594        self.generate_expression(&e.this)?;
26595        if let Some(expression) = &e.expression {
26596            self.write(", ");
26597            self.generate_expression(expression)?;
26598        }
26599        self.write(")");
26600        Ok(())
26601    }
26602
26603    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
26604        // APPROX_TOP_K(this[, expression][, counters])
26605        self.write_keyword("APPROX_TOP_K");
26606        self.write("(");
26607        self.generate_expression(&e.this)?;
26608        if let Some(expression) = &e.expression {
26609            self.write(", ");
26610            self.generate_expression(expression)?;
26611        }
26612        if let Some(counters) = &e.counters {
26613            self.write(", ");
26614            self.generate_expression(counters)?;
26615        }
26616        self.write(")");
26617        Ok(())
26618    }
26619
26620    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
26621        // APPROX_TOP_K_ACCUMULATE(this[, expression])
26622        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
26623        self.write("(");
26624        self.generate_expression(&e.this)?;
26625        if let Some(expression) = &e.expression {
26626            self.write(", ");
26627            self.generate_expression(expression)?;
26628        }
26629        self.write(")");
26630        Ok(())
26631    }
26632
26633    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
26634        // APPROX_TOP_K_COMBINE(this[, expression])
26635        self.write_keyword("APPROX_TOP_K_COMBINE");
26636        self.write("(");
26637        self.generate_expression(&e.this)?;
26638        if let Some(expression) = &e.expression {
26639            self.write(", ");
26640            self.generate_expression(expression)?;
26641        }
26642        self.write(")");
26643        Ok(())
26644    }
26645
26646    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
26647        // APPROX_TOP_K_ESTIMATE(this[, expression])
26648        self.write_keyword("APPROX_TOP_K_ESTIMATE");
26649        self.write("(");
26650        self.generate_expression(&e.this)?;
26651        if let Some(expression) = &e.expression {
26652            self.write(", ");
26653            self.generate_expression(expression)?;
26654        }
26655        self.write(")");
26656        Ok(())
26657    }
26658
26659    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
26660        // APPROX_TOP_SUM(this, expression[, count])
26661        self.write_keyword("APPROX_TOP_SUM");
26662        self.write("(");
26663        self.generate_expression(&e.this)?;
26664        self.write(", ");
26665        self.generate_expression(&e.expression)?;
26666        if let Some(count) = &e.count {
26667            self.write(", ");
26668            self.generate_expression(count)?;
26669        }
26670        self.write(")");
26671        Ok(())
26672    }
26673
26674    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
26675        // ARG_MAX(this, expression[, count])
26676        self.write_keyword("ARG_MAX");
26677        self.write("(");
26678        self.generate_expression(&e.this)?;
26679        self.write(", ");
26680        self.generate_expression(&e.expression)?;
26681        if let Some(count) = &e.count {
26682            self.write(", ");
26683            self.generate_expression(count)?;
26684        }
26685        self.write(")");
26686        Ok(())
26687    }
26688
26689    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
26690        // ARG_MIN(this, expression[, count])
26691        self.write_keyword("ARG_MIN");
26692        self.write("(");
26693        self.generate_expression(&e.this)?;
26694        self.write(", ");
26695        self.generate_expression(&e.expression)?;
26696        if let Some(count) = &e.count {
26697            self.write(", ");
26698            self.generate_expression(count)?;
26699        }
26700        self.write(")");
26701        Ok(())
26702    }
26703
26704    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
26705        // ARRAY_ALL(this, expression)
26706        self.write_keyword("ARRAY_ALL");
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_any(&mut self, e: &ArrayAny) -> Result<()> {
26716        // ARRAY_ANY(this, expression) - fallback implementation
26717        self.write_keyword("ARRAY_ANY");
26718        self.write("(");
26719        self.generate_expression(&e.this)?;
26720        self.write(", ");
26721        self.generate_expression(&e.expression)?;
26722        self.write(")");
26723        Ok(())
26724    }
26725
26726    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
26727        // ARRAY_CONSTRUCT_COMPACT(expressions...)
26728        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
26729        self.write("(");
26730        for (i, expr) in e.expressions.iter().enumerate() {
26731            if i > 0 {
26732                self.write(", ");
26733            }
26734            self.generate_expression(expr)?;
26735        }
26736        self.write(")");
26737        Ok(())
26738    }
26739
26740    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
26741        // ARRAY_SUM(this[, expression])
26742        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26743            self.write("arraySum");
26744        } else {
26745            self.write_keyword("ARRAY_SUM");
26746        }
26747        self.write("(");
26748        self.generate_expression(&e.this)?;
26749        if let Some(expression) = &e.expression {
26750            self.write(", ");
26751            self.generate_expression(expression)?;
26752        }
26753        self.write(")");
26754        Ok(())
26755    }
26756
26757    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
26758        // Python: return f"{this} AT {index}"
26759        self.generate_expression(&e.this)?;
26760        self.write_space();
26761        self.write_keyword("AT");
26762        self.write_space();
26763        self.generate_expression(&e.expression)?;
26764        Ok(())
26765    }
26766
26767    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
26768        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
26769        self.write_keyword("ATTACH");
26770        if e.exists {
26771            self.write_space();
26772            self.write_keyword("IF NOT EXISTS");
26773        }
26774        self.write_space();
26775        self.generate_expression(&e.this)?;
26776        if !e.expressions.is_empty() {
26777            self.write(" (");
26778            for (i, expr) in e.expressions.iter().enumerate() {
26779                if i > 0 {
26780                    self.write(", ");
26781                }
26782                self.generate_expression(expr)?;
26783            }
26784            self.write(")");
26785        }
26786        Ok(())
26787    }
26788
26789    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
26790        // AttachOption: this [expression]
26791        // Python sqlglot: no equals sign, just space-separated
26792        self.generate_expression(&e.this)?;
26793        if let Some(expression) = &e.expression {
26794            self.write_space();
26795            self.generate_expression(expression)?;
26796        }
26797        Ok(())
26798    }
26799
26800    /// Generate the auto_increment keyword and options for a column definition.
26801    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
26802    /// GENERATED AS IDENTITY, etc.
26803    fn generate_auto_increment_keyword(
26804        &mut self,
26805        col: &crate::expressions::ColumnDef,
26806    ) -> Result<()> {
26807        use crate::dialects::DialectType;
26808        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
26809            self.write_keyword("IDENTITY");
26810            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26811                self.write("(");
26812                if let Some(ref start) = col.auto_increment_start {
26813                    self.generate_expression(start)?;
26814                } else {
26815                    self.write("0");
26816                }
26817                self.write(", ");
26818                if let Some(ref inc) = col.auto_increment_increment {
26819                    self.generate_expression(inc)?;
26820                } else {
26821                    self.write("1");
26822                }
26823                self.write(")");
26824            }
26825        } else if matches!(
26826            self.config.dialect,
26827            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
26828        ) {
26829            self.write_keyword("AUTOINCREMENT");
26830            if let Some(ref start) = col.auto_increment_start {
26831                self.write_space();
26832                self.write_keyword("START");
26833                self.write_space();
26834                self.generate_expression(start)?;
26835            }
26836            if let Some(ref inc) = col.auto_increment_increment {
26837                self.write_space();
26838                self.write_keyword("INCREMENT");
26839                self.write_space();
26840                self.generate_expression(inc)?;
26841            }
26842            if let Some(order) = col.auto_increment_order {
26843                self.write_space();
26844                if order {
26845                    self.write_keyword("ORDER");
26846                } else {
26847                    self.write_keyword("NOORDER");
26848                }
26849            }
26850        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
26851            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
26852            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26853                self.write(" (");
26854                let mut first = true;
26855                if let Some(ref start) = col.auto_increment_start {
26856                    self.write_keyword("START WITH");
26857                    self.write_space();
26858                    self.generate_expression(start)?;
26859                    first = false;
26860                }
26861                if let Some(ref inc) = col.auto_increment_increment {
26862                    if !first {
26863                        self.write_space();
26864                    }
26865                    self.write_keyword("INCREMENT BY");
26866                    self.write_space();
26867                    self.generate_expression(inc)?;
26868                }
26869                self.write(")");
26870            }
26871        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
26872            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
26873            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
26874            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26875                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
26876            } else {
26877                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
26878            }
26879            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26880                self.write(" (");
26881                let mut first = true;
26882                if let Some(ref start) = col.auto_increment_start {
26883                    self.write_keyword("START WITH");
26884                    self.write_space();
26885                    self.generate_expression(start)?;
26886                    first = false;
26887                }
26888                if let Some(ref inc) = col.auto_increment_increment {
26889                    if !first {
26890                        self.write_space();
26891                    }
26892                    self.write_keyword("INCREMENT BY");
26893                    self.write_space();
26894                    self.generate_expression(inc)?;
26895                }
26896                self.write(")");
26897            }
26898        } else if matches!(
26899            self.config.dialect,
26900            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26901        ) {
26902            self.write_keyword("IDENTITY");
26903            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26904                self.write("(");
26905                if let Some(ref start) = col.auto_increment_start {
26906                    self.generate_expression(start)?;
26907                } else {
26908                    self.write("0");
26909                }
26910                self.write(", ");
26911                if let Some(ref inc) = col.auto_increment_increment {
26912                    self.generate_expression(inc)?;
26913                } else {
26914                    self.write("1");
26915                }
26916                self.write(")");
26917            }
26918        } else {
26919            self.write_keyword("AUTO_INCREMENT");
26920            if let Some(ref start) = col.auto_increment_start {
26921                self.write_space();
26922                self.write_keyword("START");
26923                self.write_space();
26924                self.generate_expression(start)?;
26925            }
26926            if let Some(ref inc) = col.auto_increment_increment {
26927                self.write_space();
26928                self.write_keyword("INCREMENT");
26929                self.write_space();
26930                self.generate_expression(inc)?;
26931            }
26932            if let Some(order) = col.auto_increment_order {
26933                self.write_space();
26934                if order {
26935                    self.write_keyword("ORDER");
26936                } else {
26937                    self.write_keyword("NOORDER");
26938                }
26939            }
26940        }
26941        Ok(())
26942    }
26943
26944    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
26945        // AUTO_INCREMENT=value
26946        self.write_keyword("AUTO_INCREMENT");
26947        self.write("=");
26948        self.generate_expression(&e.this)?;
26949        Ok(())
26950    }
26951
26952    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
26953        // AUTO_REFRESH=value
26954        self.write_keyword("AUTO_REFRESH");
26955        self.write("=");
26956        self.generate_expression(&e.this)?;
26957        Ok(())
26958    }
26959
26960    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
26961        // BACKUP YES|NO (Redshift syntax uses space, not equals)
26962        self.write_keyword("BACKUP");
26963        self.write_space();
26964        self.generate_expression(&e.this)?;
26965        Ok(())
26966    }
26967
26968    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
26969        // BASE64_DECODE_BINARY(this[, alphabet])
26970        self.write_keyword("BASE64_DECODE_BINARY");
26971        self.write("(");
26972        self.generate_expression(&e.this)?;
26973        if let Some(alphabet) = &e.alphabet {
26974            self.write(", ");
26975            self.generate_expression(alphabet)?;
26976        }
26977        self.write(")");
26978        Ok(())
26979    }
26980
26981    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
26982        // BASE64_DECODE_STRING(this[, alphabet])
26983        self.write_keyword("BASE64_DECODE_STRING");
26984        self.write("(");
26985        self.generate_expression(&e.this)?;
26986        if let Some(alphabet) = &e.alphabet {
26987            self.write(", ");
26988            self.generate_expression(alphabet)?;
26989        }
26990        self.write(")");
26991        Ok(())
26992    }
26993
26994    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
26995        // BASE64_ENCODE(this[, max_line_length][, alphabet])
26996        self.write_keyword("BASE64_ENCODE");
26997        self.write("(");
26998        self.generate_expression(&e.this)?;
26999        if let Some(max_line_length) = &e.max_line_length {
27000            self.write(", ");
27001            self.generate_expression(max_line_length)?;
27002        }
27003        if let Some(alphabet) = &e.alphabet {
27004            self.write(", ");
27005            self.generate_expression(alphabet)?;
27006        }
27007        self.write(")");
27008        Ok(())
27009    }
27010
27011    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
27012        // BLOCKCOMPRESSION=... (complex Teradata property)
27013        self.write_keyword("BLOCKCOMPRESSION");
27014        self.write("=");
27015        if let Some(autotemp) = &e.autotemp {
27016            self.write_keyword("AUTOTEMP");
27017            self.write("(");
27018            self.generate_expression(autotemp)?;
27019            self.write(")");
27020        }
27021        if let Some(always) = &e.always {
27022            self.generate_expression(always)?;
27023        }
27024        if let Some(default) = &e.default {
27025            self.generate_expression(default)?;
27026        }
27027        if let Some(manual) = &e.manual {
27028            self.generate_expression(manual)?;
27029        }
27030        if let Some(never) = &e.never {
27031            self.generate_expression(never)?;
27032        }
27033        Ok(())
27034    }
27035
27036    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
27037        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
27038        self.write("((");
27039        self.generate_expression(&e.this)?;
27040        self.write(") ");
27041        self.write_keyword("AND");
27042        self.write(" (");
27043        self.generate_expression(&e.expression)?;
27044        self.write("))");
27045        Ok(())
27046    }
27047
27048    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
27049        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
27050        self.write("((");
27051        self.generate_expression(&e.this)?;
27052        self.write(") ");
27053        self.write_keyword("OR");
27054        self.write(" (");
27055        self.generate_expression(&e.expression)?;
27056        self.write("))");
27057        Ok(())
27058    }
27059
27060    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
27061        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
27062        self.write_keyword("BUILD");
27063        self.write_space();
27064        self.generate_expression(&e.this)?;
27065        Ok(())
27066    }
27067
27068    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
27069        // Byte string literal like B'...' or X'...'
27070        self.generate_expression(&e.this)?;
27071        Ok(())
27072    }
27073
27074    fn generate_case_specific_column_constraint(
27075        &mut self,
27076        e: &CaseSpecificColumnConstraint,
27077    ) -> Result<()> {
27078        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
27079        if e.not_.is_some() {
27080            self.write_keyword("NOT");
27081            self.write_space();
27082        }
27083        self.write_keyword("CASESPECIFIC");
27084        Ok(())
27085    }
27086
27087    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
27088        // Cast to string type (dialect-specific)
27089        self.write_keyword("CAST");
27090        self.write("(");
27091        self.generate_expression(&e.this)?;
27092        if self.config.dialect == Some(DialectType::ClickHouse) {
27093            // ClickHouse: CAST(expr, 'type_string')
27094            self.write(", ");
27095        } else {
27096            self.write_space();
27097            self.write_keyword("AS");
27098            self.write_space();
27099        }
27100        if let Some(to) = &e.to {
27101            self.generate_expression(to)?;
27102        }
27103        self.write(")");
27104        Ok(())
27105    }
27106
27107    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
27108        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
27109        // Python: f"CHANGES ({information}){at_before}{end}"
27110        self.write_keyword("CHANGES");
27111        self.write(" (");
27112        if let Some(information) = &e.information {
27113            self.write_keyword("INFORMATION");
27114            self.write(" => ");
27115            self.generate_expression(information)?;
27116        }
27117        self.write(")");
27118        // at_before and end are HistoricalData expressions that generate their own keywords
27119        if let Some(at_before) = &e.at_before {
27120            self.write(" ");
27121            self.generate_expression(at_before)?;
27122        }
27123        if let Some(end) = &e.end {
27124            self.write(" ");
27125            self.generate_expression(end)?;
27126        }
27127        Ok(())
27128    }
27129
27130    fn generate_character_set_column_constraint(
27131        &mut self,
27132        e: &CharacterSetColumnConstraint,
27133    ) -> Result<()> {
27134        // CHARACTER SET charset_name
27135        self.write_keyword("CHARACTER SET");
27136        self.write_space();
27137        self.generate_expression(&e.this)?;
27138        Ok(())
27139    }
27140
27141    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
27142        // [DEFAULT] CHARACTER SET=value
27143        if e.default.is_some() {
27144            self.write_keyword("DEFAULT");
27145            self.write_space();
27146        }
27147        self.write_keyword("CHARACTER SET");
27148        self.write("=");
27149        self.generate_expression(&e.this)?;
27150        Ok(())
27151    }
27152
27153    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
27154        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
27155        self.write_keyword("CHECK");
27156        self.write(" (");
27157        self.generate_expression(&e.this)?;
27158        self.write(")");
27159        if e.enforced.is_some() {
27160            self.write_space();
27161            self.write_keyword("ENFORCED");
27162        }
27163        Ok(())
27164    }
27165
27166    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
27167        // Python: return f"ASSUME ({self.sql(e, 'this')})"
27168        self.write_keyword("ASSUME");
27169        self.write(" (");
27170        self.generate_expression(&e.this)?;
27171        self.write(")");
27172        Ok(())
27173    }
27174
27175    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
27176        // CHECK_JSON(this)
27177        self.write_keyword("CHECK_JSON");
27178        self.write("(");
27179        self.generate_expression(&e.this)?;
27180        self.write(")");
27181        Ok(())
27182    }
27183
27184    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
27185        // CHECK_XML(this)
27186        self.write_keyword("CHECK_XML");
27187        self.write("(");
27188        self.generate_expression(&e.this)?;
27189        self.write(")");
27190        Ok(())
27191    }
27192
27193    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
27194        // CHECKSUM=[ON|OFF|DEFAULT]
27195        self.write_keyword("CHECKSUM");
27196        self.write("=");
27197        if e.on.is_some() {
27198            self.write_keyword("ON");
27199        } else if e.default.is_some() {
27200            self.write_keyword("DEFAULT");
27201        } else {
27202            self.write_keyword("OFF");
27203        }
27204        Ok(())
27205    }
27206
27207    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
27208        // Python: return f"{shallow}{keyword} {this}"
27209        if e.shallow.is_some() {
27210            self.write_keyword("SHALLOW");
27211            self.write_space();
27212        }
27213        if e.copy.is_some() {
27214            self.write_keyword("COPY");
27215        } else {
27216            self.write_keyword("CLONE");
27217        }
27218        self.write_space();
27219        self.generate_expression(&e.this)?;
27220        Ok(())
27221    }
27222
27223    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
27224        // CLUSTER BY (expressions)
27225        self.write_keyword("CLUSTER BY");
27226        self.write(" (");
27227        for (i, ord) in e.expressions.iter().enumerate() {
27228            if i > 0 {
27229                self.write(", ");
27230            }
27231            self.generate_ordered(ord)?;
27232        }
27233        self.write(")");
27234        Ok(())
27235    }
27236
27237    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
27238        // BigQuery table property: CLUSTER BY col1, col2
27239        self.write_keyword("CLUSTER BY");
27240        self.write_space();
27241        for (i, col) in e.columns.iter().enumerate() {
27242            if i > 0 {
27243                self.write(", ");
27244            }
27245            self.generate_identifier(col)?;
27246        }
27247        Ok(())
27248    }
27249
27250    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
27251        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
27252        self.write_keyword("CLUSTERED BY");
27253        self.write(" (");
27254        for (i, expr) in e.expressions.iter().enumerate() {
27255            if i > 0 {
27256                self.write(", ");
27257            }
27258            self.generate_expression(expr)?;
27259        }
27260        self.write(")");
27261        if let Some(sorted_by) = &e.sorted_by {
27262            self.write_space();
27263            self.write_keyword("SORTED BY");
27264            self.write(" (");
27265            // Unwrap Tuple to avoid double parentheses
27266            if let Expression::Tuple(t) = sorted_by.as_ref() {
27267                for (i, expr) in t.expressions.iter().enumerate() {
27268                    if i > 0 {
27269                        self.write(", ");
27270                    }
27271                    self.generate_expression(expr)?;
27272                }
27273            } else {
27274                self.generate_expression(sorted_by)?;
27275            }
27276            self.write(")");
27277        }
27278        if let Some(buckets) = &e.buckets {
27279            self.write_space();
27280            self.write_keyword("INTO");
27281            self.write_space();
27282            self.generate_expression(buckets)?;
27283            self.write_space();
27284            self.write_keyword("BUCKETS");
27285        }
27286        Ok(())
27287    }
27288
27289    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
27290        // [DEFAULT] COLLATE [=] value
27291        // BigQuery uses space: DEFAULT COLLATE 'en'
27292        // Others use equals: COLLATE='en'
27293        if e.default.is_some() {
27294            self.write_keyword("DEFAULT");
27295            self.write_space();
27296        }
27297        self.write_keyword("COLLATE");
27298        // BigQuery uses space between COLLATE and value
27299        match self.config.dialect {
27300            Some(DialectType::BigQuery) => self.write_space(),
27301            _ => self.write("="),
27302        }
27303        self.generate_expression(&e.this)?;
27304        Ok(())
27305    }
27306
27307    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
27308        // ColumnConstraint is an enum
27309        match e {
27310            ColumnConstraint::NotNull => {
27311                self.write_keyword("NOT NULL");
27312            }
27313            ColumnConstraint::Null => {
27314                self.write_keyword("NULL");
27315            }
27316            ColumnConstraint::Unique => {
27317                self.write_keyword("UNIQUE");
27318            }
27319            ColumnConstraint::PrimaryKey => {
27320                self.write_keyword("PRIMARY KEY");
27321            }
27322            ColumnConstraint::Default(expr) => {
27323                self.write_keyword("DEFAULT");
27324                self.write_space();
27325                self.generate_expression(expr)?;
27326            }
27327            ColumnConstraint::Check(expr) => {
27328                self.write_keyword("CHECK");
27329                self.write(" (");
27330                self.generate_expression(expr)?;
27331                self.write(")");
27332            }
27333            ColumnConstraint::References(fk_ref) => {
27334                if fk_ref.has_foreign_key_keywords {
27335                    self.write_keyword("FOREIGN KEY");
27336                    self.write_space();
27337                }
27338                self.write_keyword("REFERENCES");
27339                self.write_space();
27340                self.generate_table(&fk_ref.table)?;
27341                if !fk_ref.columns.is_empty() {
27342                    self.write(" (");
27343                    for (i, col) in fk_ref.columns.iter().enumerate() {
27344                        if i > 0 {
27345                            self.write(", ");
27346                        }
27347                        self.generate_identifier(col)?;
27348                    }
27349                    self.write(")");
27350                }
27351            }
27352            ColumnConstraint::GeneratedAsIdentity(gen) => {
27353                self.write_keyword("GENERATED");
27354                self.write_space();
27355                if gen.always {
27356                    self.write_keyword("ALWAYS");
27357                } else {
27358                    self.write_keyword("BY DEFAULT");
27359                    if gen.on_null {
27360                        self.write_space();
27361                        self.write_keyword("ON NULL");
27362                    }
27363                }
27364                self.write_space();
27365                self.write_keyword("AS IDENTITY");
27366            }
27367            ColumnConstraint::Collate(collation) => {
27368                self.write_keyword("COLLATE");
27369                self.write_space();
27370                self.generate_identifier(collation)?;
27371            }
27372            ColumnConstraint::Comment(comment) => {
27373                self.write_keyword("COMMENT");
27374                self.write(" '");
27375                self.write(comment);
27376                self.write("'");
27377            }
27378            ColumnConstraint::ComputedColumn(cc) => {
27379                self.generate_computed_column_inline(cc)?;
27380            }
27381            ColumnConstraint::GeneratedAsRow(gar) => {
27382                self.generate_generated_as_row_inline(gar)?;
27383            }
27384            ColumnConstraint::Tags(tags) => {
27385                self.write_keyword("TAG");
27386                self.write(" (");
27387                for (i, expr) in tags.expressions.iter().enumerate() {
27388                    if i > 0 {
27389                        self.write(", ");
27390                    }
27391                    self.generate_expression(expr)?;
27392                }
27393                self.write(")");
27394            }
27395            ColumnConstraint::Path(path_expr) => {
27396                self.write_keyword("PATH");
27397                self.write_space();
27398                self.generate_expression(path_expr)?;
27399            }
27400        }
27401        Ok(())
27402    }
27403
27404    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
27405        // ColumnPosition is an enum
27406        match e {
27407            ColumnPosition::First => {
27408                self.write_keyword("FIRST");
27409            }
27410            ColumnPosition::After(ident) => {
27411                self.write_keyword("AFTER");
27412                self.write_space();
27413                self.generate_identifier(ident)?;
27414            }
27415        }
27416        Ok(())
27417    }
27418
27419    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
27420        // column(prefix)
27421        self.generate_expression(&e.this)?;
27422        self.write("(");
27423        self.generate_expression(&e.expression)?;
27424        self.write(")");
27425        Ok(())
27426    }
27427
27428    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
27429        // If unpack is true, this came from * COLUMNS(pattern)
27430        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
27431        if let Some(ref unpack) = e.unpack {
27432            if let Expression::Boolean(b) = unpack.as_ref() {
27433                if b.value {
27434                    self.write("*");
27435                }
27436            }
27437        }
27438        self.write_keyword("COLUMNS");
27439        self.write("(");
27440        self.generate_expression(&e.this)?;
27441        self.write(")");
27442        Ok(())
27443    }
27444
27445    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
27446        // Combined aggregate: FUNC(args) combined
27447        self.generate_expression(&e.this)?;
27448        self.write("(");
27449        for (i, expr) in e.expressions.iter().enumerate() {
27450            if i > 0 {
27451                self.write(", ");
27452            }
27453            self.generate_expression(expr)?;
27454        }
27455        self.write(")");
27456        Ok(())
27457    }
27458
27459    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
27460        // Combined parameterized aggregate: FUNC(params)(expressions)
27461        self.generate_expression(&e.this)?;
27462        self.write("(");
27463        for (i, param) in e.params.iter().enumerate() {
27464            if i > 0 {
27465                self.write(", ");
27466            }
27467            self.generate_expression(param)?;
27468        }
27469        self.write(")(");
27470        for (i, expr) in e.expressions.iter().enumerate() {
27471            if i > 0 {
27472                self.write(", ");
27473            }
27474            self.generate_expression(expr)?;
27475        }
27476        self.write(")");
27477        Ok(())
27478    }
27479
27480    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
27481        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
27482        self.write_keyword("COMMIT");
27483
27484        // TSQL always uses COMMIT TRANSACTION
27485        if e.this.is_none()
27486            && matches!(
27487                self.config.dialect,
27488                Some(DialectType::TSQL) | Some(DialectType::Fabric)
27489            )
27490        {
27491            self.write_space();
27492            self.write_keyword("TRANSACTION");
27493        }
27494
27495        // Check if this has TRANSACTION keyword or transaction name
27496        if let Some(this) = &e.this {
27497            // Check if it's just the "TRANSACTION" marker or an actual transaction name
27498            let is_transaction_marker = matches!(
27499                this.as_ref(),
27500                Expression::Identifier(id) if id.name == "TRANSACTION"
27501            );
27502
27503            self.write_space();
27504            self.write_keyword("TRANSACTION");
27505
27506            // If it's a real transaction name, output it
27507            if !is_transaction_marker {
27508                self.write_space();
27509                self.generate_expression(this)?;
27510            }
27511        }
27512
27513        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
27514        if let Some(durability) = &e.durability {
27515            self.write_space();
27516            self.write_keyword("WITH");
27517            self.write(" (");
27518            self.write_keyword("DELAYED_DURABILITY");
27519            self.write(" = ");
27520            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
27521                self.write_keyword("ON");
27522            } else {
27523                self.write_keyword("OFF");
27524            }
27525            self.write(")");
27526        }
27527
27528        // Output AND [NO] CHAIN
27529        if let Some(chain) = &e.chain {
27530            self.write_space();
27531            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
27532                self.write_keyword("AND NO CHAIN");
27533            } else {
27534                self.write_keyword("AND CHAIN");
27535            }
27536        }
27537        Ok(())
27538    }
27539
27540    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
27541        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
27542        self.write("[");
27543        self.generate_expression(&e.this)?;
27544        self.write_space();
27545        self.write_keyword("FOR");
27546        self.write_space();
27547        self.generate_expression(&e.expression)?;
27548        // Handle optional position variable (for enumerate-like syntax)
27549        if let Some(pos) = &e.position {
27550            self.write(", ");
27551            self.generate_expression(pos)?;
27552        }
27553        if let Some(iterator) = &e.iterator {
27554            self.write_space();
27555            self.write_keyword("IN");
27556            self.write_space();
27557            self.generate_expression(iterator)?;
27558        }
27559        if let Some(condition) = &e.condition {
27560            self.write_space();
27561            self.write_keyword("IF");
27562            self.write_space();
27563            self.generate_expression(condition)?;
27564        }
27565        self.write("]");
27566        Ok(())
27567    }
27568
27569    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
27570        // COMPRESS(this[, method])
27571        self.write_keyword("COMPRESS");
27572        self.write("(");
27573        self.generate_expression(&e.this)?;
27574        if let Some(method) = &e.method {
27575            self.write(", '");
27576            self.write(method);
27577            self.write("'");
27578        }
27579        self.write(")");
27580        Ok(())
27581    }
27582
27583    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
27584        // Python: return f"COMPRESS {this}"
27585        self.write_keyword("COMPRESS");
27586        if let Some(this) = &e.this {
27587            self.write_space();
27588            self.generate_expression(this)?;
27589        }
27590        Ok(())
27591    }
27592
27593    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
27594        // Python: return f"AS {this}{persisted}"
27595        self.write_keyword("AS");
27596        self.write_space();
27597        self.generate_expression(&e.this)?;
27598        if e.not_null.is_some() {
27599            self.write_space();
27600            self.write_keyword("PERSISTED NOT NULL");
27601        } else if e.persisted.is_some() {
27602            self.write_space();
27603            self.write_keyword("PERSISTED");
27604        }
27605        Ok(())
27606    }
27607
27608    /// Generate a ComputedColumn constraint inline within a column definition.
27609    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
27610    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
27611    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
27612        let computed_expr = if matches!(
27613            self.config.dialect,
27614            Some(DialectType::TSQL) | Some(DialectType::Fabric)
27615        ) {
27616            match &*cc.expression {
27617                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
27618                {
27619                    let wrapped = Expression::Cast(Box::new(Cast {
27620                        this: y.this.clone(),
27621                        to: DataType::Date,
27622                        trailing_comments: Vec::new(),
27623                        double_colon_syntax: false,
27624                        format: None,
27625                        default: None,
27626                        inferred_type: None,
27627                    }));
27628                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
27629                }
27630                Expression::Function(f)
27631                    if f.name.eq_ignore_ascii_case("YEAR")
27632                        && f.args.len() == 1
27633                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
27634                {
27635                    let wrapped = Expression::Cast(Box::new(Cast {
27636                        this: f.args[0].clone(),
27637                        to: DataType::Date,
27638                        trailing_comments: Vec::new(),
27639                        double_colon_syntax: false,
27640                        format: None,
27641                        default: None,
27642                        inferred_type: None,
27643                    }));
27644                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
27645                }
27646                _ => *cc.expression.clone(),
27647            }
27648        } else {
27649            *cc.expression.clone()
27650        };
27651
27652        match cc.persistence_kind.as_deref() {
27653            Some("STORED") | Some("VIRTUAL") => {
27654                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
27655                self.write_keyword("GENERATED ALWAYS AS");
27656                self.write(" (");
27657                self.generate_expression(&computed_expr)?;
27658                self.write(")");
27659                self.write_space();
27660                if cc.persisted {
27661                    self.write_keyword("STORED");
27662                } else {
27663                    self.write_keyword("VIRTUAL");
27664                }
27665            }
27666            Some("PERSISTED") => {
27667                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
27668                self.write_keyword("AS");
27669                self.write(" (");
27670                self.generate_expression(&computed_expr)?;
27671                self.write(")");
27672                self.write_space();
27673                self.write_keyword("PERSISTED");
27674                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
27675                if let Some(ref dt) = cc.data_type {
27676                    self.write_space();
27677                    self.generate_data_type(dt)?;
27678                }
27679                if cc.not_null {
27680                    self.write_space();
27681                    self.write_keyword("NOT NULL");
27682                }
27683            }
27684            _ => {
27685                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
27686                // TSQL computed column without PERSISTED: AS (expr)
27687                if matches!(
27688                    self.config.dialect,
27689                    Some(DialectType::Spark)
27690                        | Some(DialectType::Databricks)
27691                        | Some(DialectType::Hive)
27692                ) {
27693                    self.write_keyword("GENERATED ALWAYS AS");
27694                    self.write(" (");
27695                    self.generate_expression(&computed_expr)?;
27696                    self.write(")");
27697                } else if matches!(
27698                    self.config.dialect,
27699                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27700                ) {
27701                    self.write_keyword("AS");
27702                    let omit_parens = matches!(computed_expr, Expression::Year(_))
27703                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
27704                    if omit_parens {
27705                        self.write_space();
27706                        self.generate_expression(&computed_expr)?;
27707                    } else {
27708                        self.write(" (");
27709                        self.generate_expression(&computed_expr)?;
27710                        self.write(")");
27711                    }
27712                } else {
27713                    self.write_keyword("AS");
27714                    self.write(" (");
27715                    self.generate_expression(&computed_expr)?;
27716                    self.write(")");
27717                }
27718            }
27719        }
27720        Ok(())
27721    }
27722
27723    /// Generate a GeneratedAsRow constraint inline within a column definition.
27724    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
27725    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
27726        self.write_keyword("GENERATED ALWAYS AS ROW ");
27727        if gar.start {
27728            self.write_keyword("START");
27729        } else {
27730            self.write_keyword("END");
27731        }
27732        if gar.hidden {
27733            self.write_space();
27734            self.write_keyword("HIDDEN");
27735        }
27736        Ok(())
27737    }
27738
27739    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
27740    fn generate_system_versioning_content(
27741        &mut self,
27742        e: &WithSystemVersioningProperty,
27743    ) -> Result<()> {
27744        let mut parts = Vec::new();
27745
27746        if let Some(this) = &e.this {
27747            let mut s = String::from("HISTORY_TABLE=");
27748            let mut gen = Generator::with_arc_config(self.config.clone());
27749            gen.generate_expression(this)?;
27750            s.push_str(&gen.output);
27751            parts.push(s);
27752        }
27753
27754        if let Some(data_consistency) = &e.data_consistency {
27755            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
27756            let mut gen = Generator::with_arc_config(self.config.clone());
27757            gen.generate_expression(data_consistency)?;
27758            s.push_str(&gen.output);
27759            parts.push(s);
27760        }
27761
27762        if let Some(retention_period) = &e.retention_period {
27763            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
27764            let mut gen = Generator::with_arc_config(self.config.clone());
27765            gen.generate_expression(retention_period)?;
27766            s.push_str(&gen.output);
27767            parts.push(s);
27768        }
27769
27770        self.write_keyword("SYSTEM_VERSIONING");
27771        self.write("=");
27772
27773        if !parts.is_empty() {
27774            self.write_keyword("ON");
27775            self.write("(");
27776            self.write(&parts.join(", "));
27777            self.write(")");
27778        } else if e.on.is_some() {
27779            self.write_keyword("ON");
27780        } else {
27781            self.write_keyword("OFF");
27782        }
27783
27784        Ok(())
27785    }
27786
27787    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
27788        // Conditional INSERT for multi-table inserts
27789        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
27790        if e.else_.is_some() {
27791            self.write_keyword("ELSE");
27792            self.write_space();
27793        } else if let Some(expression) = &e.expression {
27794            self.write_keyword("WHEN");
27795            self.write_space();
27796            self.generate_expression(expression)?;
27797            self.write_space();
27798            self.write_keyword("THEN");
27799            self.write_space();
27800        }
27801
27802        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
27803        // without the "INSERT " prefix
27804        if let Expression::Insert(insert) = e.this.as_ref() {
27805            self.write_keyword("INTO");
27806            self.write_space();
27807            self.generate_table(&insert.table)?;
27808
27809            // Optional column list
27810            if !insert.columns.is_empty() {
27811                self.write(" (");
27812                for (i, col) in insert.columns.iter().enumerate() {
27813                    if i > 0 {
27814                        self.write(", ");
27815                    }
27816                    self.generate_identifier(col)?;
27817                }
27818                self.write(")");
27819            }
27820
27821            // Optional VALUES clause
27822            if !insert.values.is_empty() {
27823                self.write_space();
27824                self.write_keyword("VALUES");
27825                for (row_idx, row) in insert.values.iter().enumerate() {
27826                    if row_idx > 0 {
27827                        self.write(", ");
27828                    }
27829                    self.write(" (");
27830                    for (i, val) in row.iter().enumerate() {
27831                        if i > 0 {
27832                            self.write(", ");
27833                        }
27834                        self.generate_expression(val)?;
27835                    }
27836                    self.write(")");
27837                }
27838            }
27839        } else {
27840            // Fallback for non-Insert expressions
27841            self.generate_expression(&e.this)?;
27842        }
27843        Ok(())
27844    }
27845
27846    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
27847        // Python: return f"CONSTRAINT {this} {expressions}"
27848        self.write_keyword("CONSTRAINT");
27849        self.write_space();
27850        self.generate_expression(&e.this)?;
27851        if !e.expressions.is_empty() {
27852            self.write_space();
27853            for (i, expr) in e.expressions.iter().enumerate() {
27854                if i > 0 {
27855                    self.write_space();
27856                }
27857                self.generate_expression(expr)?;
27858            }
27859        }
27860        Ok(())
27861    }
27862
27863    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
27864        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
27865        self.write_keyword("CONVERT_TIMEZONE");
27866        self.write("(");
27867        let mut first = true;
27868        if let Some(source_tz) = &e.source_tz {
27869            self.generate_expression(source_tz)?;
27870            first = false;
27871        }
27872        if let Some(target_tz) = &e.target_tz {
27873            if !first {
27874                self.write(", ");
27875            }
27876            self.generate_expression(target_tz)?;
27877            first = false;
27878        }
27879        if let Some(timestamp) = &e.timestamp {
27880            if !first {
27881                self.write(", ");
27882            }
27883            self.generate_expression(timestamp)?;
27884        }
27885        self.write(")");
27886        Ok(())
27887    }
27888
27889    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
27890        // CONVERT(this USING dest)
27891        self.write_keyword("CONVERT");
27892        self.write("(");
27893        self.generate_expression(&e.this)?;
27894        if let Some(dest) = &e.dest {
27895            self.write_space();
27896            self.write_keyword("USING");
27897            self.write_space();
27898            self.generate_expression(dest)?;
27899        }
27900        self.write(")");
27901        Ok(())
27902    }
27903
27904    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
27905        self.write_keyword("COPY");
27906        if e.is_into {
27907            self.write_space();
27908            self.write_keyword("INTO");
27909        }
27910        self.write_space();
27911
27912        // Generate target table or query (or stage for COPY INTO @stage)
27913        if let Expression::Literal(lit) = &e.this {
27914            if let Literal::String(s) = lit.as_ref() {
27915                if s.starts_with('@') {
27916                    self.write(s);
27917                } else {
27918                    self.generate_expression(&e.this)?;
27919                }
27920            }
27921        } else {
27922            self.generate_expression(&e.this)?;
27923        }
27924
27925        // FROM or TO based on kind
27926        if e.kind {
27927            // kind=true means FROM (loading into table)
27928            if self.config.pretty {
27929                self.write_newline();
27930            } else {
27931                self.write_space();
27932            }
27933            self.write_keyword("FROM");
27934            self.write_space();
27935        } else if !e.files.is_empty() {
27936            // kind=false means TO (exporting)
27937            if self.config.pretty {
27938                self.write_newline();
27939            } else {
27940                self.write_space();
27941            }
27942            self.write_keyword("TO");
27943            self.write_space();
27944        }
27945
27946        // Generate source/destination files
27947        for (i, file) in e.files.iter().enumerate() {
27948            if i > 0 {
27949                self.write_space();
27950            }
27951            // For stage references (strings starting with @), output without quotes
27952            if let Expression::Literal(lit) = file {
27953                if let Literal::String(s) = lit.as_ref() {
27954                    if s.starts_with('@') {
27955                        self.write(s);
27956                    } else {
27957                        self.generate_expression(file)?;
27958                    }
27959                }
27960            } else if let Expression::Identifier(id) = file {
27961                // Backtick-quoted file path (Databricks style: `s3://link`)
27962                if id.quoted {
27963                    self.write("`");
27964                    self.write(&id.name);
27965                    self.write("`");
27966                } else {
27967                    self.generate_expression(file)?;
27968                }
27969            } else {
27970                self.generate_expression(file)?;
27971            }
27972        }
27973
27974        // Generate credentials if present (Snowflake style - not wrapped in WITH)
27975        if !e.with_wrapped {
27976            if let Some(ref creds) = e.credentials {
27977                if let Some(ref storage) = creds.storage {
27978                    if self.config.pretty {
27979                        self.write_newline();
27980                    } else {
27981                        self.write_space();
27982                    }
27983                    self.write_keyword("STORAGE_INTEGRATION");
27984                    self.write(" = ");
27985                    self.write(storage);
27986                }
27987                if creds.credentials.is_empty() {
27988                    // Empty credentials: CREDENTIALS = ()
27989                    if self.config.pretty {
27990                        self.write_newline();
27991                    } else {
27992                        self.write_space();
27993                    }
27994                    self.write_keyword("CREDENTIALS");
27995                    self.write(" = ()");
27996                } else {
27997                    if self.config.pretty {
27998                        self.write_newline();
27999                    } else {
28000                        self.write_space();
28001                    }
28002                    self.write_keyword("CREDENTIALS");
28003                    // Check if this is Redshift-style (single value with empty key)
28004                    // vs Snowflake-style (multiple key=value pairs)
28005                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
28006                        // Redshift style: CREDENTIALS 'value'
28007                        self.write(" '");
28008                        self.write(&creds.credentials[0].1);
28009                        self.write("'");
28010                    } else {
28011                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
28012                        self.write(" = (");
28013                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
28014                            if i > 0 {
28015                                self.write_space();
28016                            }
28017                            self.write(k);
28018                            self.write("='");
28019                            self.write(v);
28020                            self.write("'");
28021                        }
28022                        self.write(")");
28023                    }
28024                }
28025                if let Some(ref encryption) = creds.encryption {
28026                    self.write_space();
28027                    self.write_keyword("ENCRYPTION");
28028                    self.write(" = ");
28029                    self.write(encryption);
28030                }
28031            }
28032        }
28033
28034        // Generate parameters
28035        if !e.params.is_empty() {
28036            if e.with_wrapped {
28037                // DuckDB/PostgreSQL/TSQL WITH (...) format
28038                self.write_space();
28039                self.write_keyword("WITH");
28040                self.write(" (");
28041                for (i, param) in e.params.iter().enumerate() {
28042                    if i > 0 {
28043                        self.write(", ");
28044                    }
28045                    self.generate_copy_param_with_format(param)?;
28046                }
28047                self.write(")");
28048            } else {
28049                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
28050                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
28051                // For Snowflake: KEY = VALUE
28052                for param in &e.params {
28053                    if self.config.pretty {
28054                        self.write_newline();
28055                    } else {
28056                        self.write_space();
28057                    }
28058                    // Preserve original case of parameter name (important for Redshift COPY options)
28059                    self.write(&param.name);
28060                    if let Some(ref value) = param.value {
28061                        // Use = only if it was present in the original (param.eq)
28062                        if param.eq {
28063                            self.write(" = ");
28064                        } else {
28065                            self.write(" ");
28066                        }
28067                        if !param.values.is_empty() {
28068                            self.write("(");
28069                            for (i, v) in param.values.iter().enumerate() {
28070                                if i > 0 {
28071                                    self.write_space();
28072                                }
28073                                self.generate_copy_nested_param(v)?;
28074                            }
28075                            self.write(")");
28076                        } else {
28077                            // For COPY parameter values, output identifiers without quoting
28078                            self.generate_copy_param_value(value)?;
28079                        }
28080                    } else if !param.values.is_empty() {
28081                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
28082                        if param.eq {
28083                            self.write(" = (");
28084                        } else {
28085                            self.write(" (");
28086                        }
28087                        // Determine separator for values inside parentheses:
28088                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
28089                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
28090                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
28091                        let is_key_value_pairs = param
28092                            .values
28093                            .first()
28094                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
28095                        let sep = if is_key_value_pairs && param.eq {
28096                            " "
28097                        } else {
28098                            ", "
28099                        };
28100                        for (i, v) in param.values.iter().enumerate() {
28101                            if i > 0 {
28102                                self.write(sep);
28103                            }
28104                            self.generate_copy_nested_param(v)?;
28105                        }
28106                        self.write(")");
28107                    }
28108                }
28109            }
28110        }
28111
28112        Ok(())
28113    }
28114
28115    /// Generate a COPY parameter in WITH (...) format
28116    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
28117    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
28118        self.write_keyword(&param.name);
28119        if !param.values.is_empty() {
28120            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
28121            self.write(" = (");
28122            for (i, v) in param.values.iter().enumerate() {
28123                if i > 0 {
28124                    self.write(", ");
28125                }
28126                self.generate_copy_nested_param(v)?;
28127            }
28128            self.write(")");
28129        } else if let Some(ref value) = param.value {
28130            if param.eq {
28131                self.write(" = ");
28132            } else {
28133                self.write(" ");
28134            }
28135            self.generate_expression(value)?;
28136        }
28137        Ok(())
28138    }
28139
28140    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
28141    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
28142        match expr {
28143            Expression::Eq(eq) => {
28144                // Generate key
28145                match &eq.left {
28146                    Expression::Column(c) => self.write(&c.name.name),
28147                    _ => self.generate_expression(&eq.left)?,
28148                }
28149                self.write("=");
28150                // Generate value
28151                match &eq.right {
28152                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
28153                        let Literal::String(s) = lit.as_ref() else {
28154                            unreachable!()
28155                        };
28156                        self.write("'");
28157                        self.write(s);
28158                        self.write("'");
28159                    }
28160                    Expression::Tuple(t) => {
28161                        // For lists like NULL_IF=('', 'str1')
28162                        self.write("(");
28163                        if self.config.pretty {
28164                            self.write_newline();
28165                            self.indent_level += 1;
28166                            for (i, item) in t.expressions.iter().enumerate() {
28167                                if i > 0 {
28168                                    self.write(", ");
28169                                }
28170                                self.write_indent();
28171                                self.generate_expression(item)?;
28172                            }
28173                            self.write_newline();
28174                            self.indent_level -= 1;
28175                        } else {
28176                            for (i, item) in t.expressions.iter().enumerate() {
28177                                if i > 0 {
28178                                    self.write(", ");
28179                                }
28180                                self.generate_expression(item)?;
28181                            }
28182                        }
28183                        self.write(")");
28184                    }
28185                    _ => self.generate_expression(&eq.right)?,
28186                }
28187                Ok(())
28188            }
28189            Expression::Column(c) => {
28190                // Standalone keyword like COMPRESSION
28191                self.write(&c.name.name);
28192                Ok(())
28193            }
28194            _ => self.generate_expression(expr),
28195        }
28196    }
28197
28198    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
28199    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
28200    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
28201        match expr {
28202            Expression::Column(c) => {
28203                // Output identifier, preserving quotes if originally quoted
28204                if c.name.quoted {
28205                    self.write("\"");
28206                    self.write(&c.name.name);
28207                    self.write("\"");
28208                } else {
28209                    self.write(&c.name.name);
28210                }
28211                Ok(())
28212            }
28213            Expression::Identifier(id) => {
28214                // Output identifier, preserving quotes if originally quoted
28215                if id.quoted {
28216                    self.write("\"");
28217                    self.write(&id.name);
28218                    self.write("\"");
28219                } else {
28220                    self.write(&id.name);
28221                }
28222                Ok(())
28223            }
28224            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
28225                let Literal::String(s) = lit.as_ref() else {
28226                    unreachable!()
28227                };
28228                // Output string with quotes
28229                self.write("'");
28230                self.write(s);
28231                self.write("'");
28232                Ok(())
28233            }
28234            _ => self.generate_expression(expr),
28235        }
28236    }
28237
28238    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
28239        self.write_keyword(&e.name);
28240        if let Some(ref value) = e.value {
28241            if e.eq {
28242                self.write(" = ");
28243            } else {
28244                self.write(" ");
28245            }
28246            self.generate_expression(value)?;
28247        }
28248        if !e.values.is_empty() {
28249            if e.eq {
28250                self.write(" = ");
28251            } else {
28252                self.write(" ");
28253            }
28254            self.write("(");
28255            for (i, v) in e.values.iter().enumerate() {
28256                if i > 0 {
28257                    self.write(", ");
28258                }
28259                self.generate_expression(v)?;
28260            }
28261            self.write(")");
28262        }
28263        Ok(())
28264    }
28265
28266    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
28267        // CORR(this, expression)
28268        self.write_keyword("CORR");
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_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
28278        // COSINE_DISTANCE(this, expression)
28279        self.write_keyword("COSINE_DISTANCE");
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_pop(&mut self, e: &CovarPop) -> Result<()> {
28289        // COVAR_POP(this, expression)
28290        self.write_keyword("COVAR_POP");
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_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
28300        // COVAR_SAMP(this, expression)
28301        self.write_keyword("COVAR_SAMP");
28302        self.write("(");
28303        self.generate_expression(&e.this)?;
28304        self.write(", ");
28305        self.generate_expression(&e.expression)?;
28306        self.write(")");
28307        Ok(())
28308    }
28309
28310    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
28311        // CREDENTIALS (key1='value1', key2='value2')
28312        self.write_keyword("CREDENTIALS");
28313        self.write(" (");
28314        for (i, (key, value)) in e.credentials.iter().enumerate() {
28315            if i > 0 {
28316                self.write(", ");
28317            }
28318            self.write(key);
28319            self.write("='");
28320            self.write(value);
28321            self.write("'");
28322        }
28323        self.write(")");
28324        Ok(())
28325    }
28326
28327    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
28328        // CREDENTIALS=(expressions)
28329        self.write_keyword("CREDENTIALS");
28330        self.write("=(");
28331        for (i, expr) in e.expressions.iter().enumerate() {
28332            if i > 0 {
28333                self.write(", ");
28334            }
28335            self.generate_expression(expr)?;
28336        }
28337        self.write(")");
28338        Ok(())
28339    }
28340
28341    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
28342        use crate::dialects::DialectType;
28343
28344        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
28345        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
28346        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
28347            self.generate_expression(&e.this)?;
28348            self.write_space();
28349            self.write_keyword("AS");
28350            self.write_space();
28351            self.generate_identifier(&e.alias)?;
28352            return Ok(());
28353        }
28354        self.write(&e.alias.name);
28355
28356        // BigQuery doesn't support column aliases in CTE definitions
28357        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
28358
28359        if !e.columns.is_empty() && !skip_cte_columns {
28360            self.write("(");
28361            for (i, col) in e.columns.iter().enumerate() {
28362                if i > 0 {
28363                    self.write(", ");
28364                }
28365                self.write(&col.name);
28366            }
28367            self.write(")");
28368        }
28369        // USING KEY (columns) for DuckDB recursive CTEs
28370        if !e.key_expressions.is_empty() {
28371            self.write_space();
28372            self.write_keyword("USING KEY");
28373            self.write(" (");
28374            for (i, key) in e.key_expressions.iter().enumerate() {
28375                if i > 0 {
28376                    self.write(", ");
28377                }
28378                self.write(&key.name);
28379            }
28380            self.write(")");
28381        }
28382        self.write_space();
28383        self.write_keyword("AS");
28384        self.write_space();
28385        if let Some(materialized) = e.materialized {
28386            if materialized {
28387                self.write_keyword("MATERIALIZED");
28388            } else {
28389                self.write_keyword("NOT MATERIALIZED");
28390            }
28391            self.write_space();
28392        }
28393        self.write("(");
28394        self.generate_expression(&e.this)?;
28395        self.write(")");
28396        Ok(())
28397    }
28398
28399    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
28400        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
28401        if e.expressions.is_empty() {
28402            self.write_keyword("WITH CUBE");
28403        } else {
28404            self.write_keyword("CUBE");
28405            self.write("(");
28406            for (i, expr) in e.expressions.iter().enumerate() {
28407                if i > 0 {
28408                    self.write(", ");
28409                }
28410                self.generate_expression(expr)?;
28411            }
28412            self.write(")");
28413        }
28414        Ok(())
28415    }
28416
28417    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
28418        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
28419        self.write_keyword("CURRENT_DATETIME");
28420        if let Some(this) = &e.this {
28421            self.write("(");
28422            self.generate_expression(this)?;
28423            self.write(")");
28424        }
28425        Ok(())
28426    }
28427
28428    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
28429        // CURRENT_SCHEMA - no arguments
28430        self.write_keyword("CURRENT_SCHEMA");
28431        Ok(())
28432    }
28433
28434    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
28435        // CURRENT_SCHEMAS(include_implicit)
28436        self.write_keyword("CURRENT_SCHEMAS");
28437        self.write("(");
28438        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
28439        if !matches!(
28440            self.config.dialect,
28441            Some(crate::dialects::DialectType::Snowflake)
28442        ) {
28443            if let Some(this) = &e.this {
28444                self.generate_expression(this)?;
28445            }
28446        }
28447        self.write(")");
28448        Ok(())
28449    }
28450
28451    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
28452        // CURRENT_USER or CURRENT_USER()
28453        self.write_keyword("CURRENT_USER");
28454        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
28455        let needs_parens = e.this.is_some()
28456            || matches!(
28457                self.config.dialect,
28458                Some(DialectType::Snowflake)
28459                    | Some(DialectType::Spark)
28460                    | Some(DialectType::Hive)
28461                    | Some(DialectType::DuckDB)
28462                    | Some(DialectType::BigQuery)
28463                    | Some(DialectType::MySQL)
28464                    | Some(DialectType::Databricks)
28465            );
28466        if needs_parens {
28467            self.write("()");
28468        }
28469        Ok(())
28470    }
28471
28472    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
28473        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
28474        if self.config.dialect == Some(DialectType::Solr) {
28475            self.generate_expression(&e.this)?;
28476            self.write(" ");
28477            self.write_keyword("OR");
28478            self.write(" ");
28479            self.generate_expression(&e.expression)?;
28480        } else if self.config.dialect == Some(DialectType::MySQL) {
28481            self.generate_mysql_concat_from_dpipe(e)?;
28482        } else {
28483            // String concatenation: this || expression
28484            self.generate_expression(&e.this)?;
28485            self.write(" || ");
28486            self.generate_expression(&e.expression)?;
28487        }
28488        Ok(())
28489    }
28490
28491    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
28492        // DATABLOCKSIZE=... (Teradata)
28493        self.write_keyword("DATABLOCKSIZE");
28494        self.write("=");
28495        if let Some(size) = e.size {
28496            self.write(&size.to_string());
28497            if let Some(units) = &e.units {
28498                self.write_space();
28499                self.generate_expression(units)?;
28500            }
28501        } else if e.minimum.is_some() {
28502            self.write_keyword("MINIMUM");
28503        } else if e.maximum.is_some() {
28504            self.write_keyword("MAXIMUM");
28505        } else if e.default.is_some() {
28506            self.write_keyword("DEFAULT");
28507        }
28508        Ok(())
28509    }
28510
28511    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
28512        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
28513        self.write_keyword("DATA_DELETION");
28514        self.write("=");
28515
28516        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
28517        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
28518
28519        if is_on {
28520            self.write_keyword("ON");
28521            if has_options {
28522                self.write("(");
28523                let mut first = true;
28524                if let Some(filter_column) = &e.filter_column {
28525                    self.write_keyword("FILTER_COLUMN");
28526                    self.write("=");
28527                    self.generate_expression(filter_column)?;
28528                    first = false;
28529                }
28530                if let Some(retention_period) = &e.retention_period {
28531                    if !first {
28532                        self.write(", ");
28533                    }
28534                    self.write_keyword("RETENTION_PERIOD");
28535                    self.write("=");
28536                    self.generate_expression(retention_period)?;
28537                }
28538                self.write(")");
28539            }
28540        } else {
28541            self.write_keyword("OFF");
28542        }
28543        Ok(())
28544    }
28545
28546    /// Generate a Date function expression
28547    /// For Exasol: {d'value'} -> TO_DATE('value')
28548    /// For other dialects: DATE('value')
28549    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
28550        use crate::dialects::DialectType;
28551        use crate::expressions::Literal;
28552
28553        match self.config.dialect {
28554            // Exasol uses TO_DATE for Date expressions
28555            Some(DialectType::Exasol) => {
28556                self.write_keyword("TO_DATE");
28557                self.write("(");
28558                // Extract the string value from the expression if it's a string literal
28559                match &e.this {
28560                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
28561                        let Literal::String(s) = lit.as_ref() else {
28562                            unreachable!()
28563                        };
28564                        self.write("'");
28565                        self.write(s);
28566                        self.write("'");
28567                    }
28568                    _ => {
28569                        self.generate_expression(&e.this)?;
28570                    }
28571                }
28572                self.write(")");
28573            }
28574            // Standard: DATE(value)
28575            _ => {
28576                self.write_keyword("DATE");
28577                self.write("(");
28578                self.generate_expression(&e.this)?;
28579                self.write(")");
28580            }
28581        }
28582        Ok(())
28583    }
28584
28585    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
28586        // DATE_BIN(interval, timestamp[, origin])
28587        self.write_keyword("DATE_BIN");
28588        self.write("(");
28589        self.generate_expression(&e.this)?;
28590        self.write(", ");
28591        self.generate_expression(&e.expression)?;
28592        if let Some(origin) = &e.origin {
28593            self.write(", ");
28594            self.generate_expression(origin)?;
28595        }
28596        self.write(")");
28597        Ok(())
28598    }
28599
28600    fn generate_date_format_column_constraint(
28601        &mut self,
28602        e: &DateFormatColumnConstraint,
28603    ) -> Result<()> {
28604        // FORMAT 'format_string' (Teradata)
28605        self.write_keyword("FORMAT");
28606        self.write_space();
28607        self.generate_expression(&e.this)?;
28608        Ok(())
28609    }
28610
28611    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
28612        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
28613        self.write_keyword("DATE_FROM_PARTS");
28614        self.write("(");
28615        let mut first = true;
28616        if let Some(year) = &e.year {
28617            self.generate_expression(year)?;
28618            first = false;
28619        }
28620        if let Some(month) = &e.month {
28621            if !first {
28622                self.write(", ");
28623            }
28624            self.generate_expression(month)?;
28625            first = false;
28626        }
28627        if let Some(day) = &e.day {
28628            if !first {
28629                self.write(", ");
28630            }
28631            self.generate_expression(day)?;
28632        }
28633        self.write(")");
28634        Ok(())
28635    }
28636
28637    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
28638        // DATETIME(this) or DATETIME(this, expression)
28639        self.write_keyword("DATETIME");
28640        self.write("(");
28641        self.generate_expression(&e.this)?;
28642        if let Some(expr) = &e.expression {
28643            self.write(", ");
28644            self.generate_expression(expr)?;
28645        }
28646        self.write(")");
28647        Ok(())
28648    }
28649
28650    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
28651        // DATETIME_ADD(this, expression, unit)
28652        self.write_keyword("DATETIME_ADD");
28653        self.write("(");
28654        self.generate_expression(&e.this)?;
28655        self.write(", ");
28656        self.generate_expression(&e.expression)?;
28657        if let Some(unit) = &e.unit {
28658            self.write(", ");
28659            self.write_keyword(unit);
28660        }
28661        self.write(")");
28662        Ok(())
28663    }
28664
28665    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
28666        // DATETIME_DIFF(this, expression, unit)
28667        self.write_keyword("DATETIME_DIFF");
28668        self.write("(");
28669        self.generate_expression(&e.this)?;
28670        self.write(", ");
28671        self.generate_expression(&e.expression)?;
28672        if let Some(unit) = &e.unit {
28673            self.write(", ");
28674            self.write_keyword(unit);
28675        }
28676        self.write(")");
28677        Ok(())
28678    }
28679
28680    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
28681        // DATETIME_SUB(this, expression, unit)
28682        self.write_keyword("DATETIME_SUB");
28683        self.write("(");
28684        self.generate_expression(&e.this)?;
28685        self.write(", ");
28686        self.generate_expression(&e.expression)?;
28687        if let Some(unit) = &e.unit {
28688            self.write(", ");
28689            self.write_keyword(unit);
28690        }
28691        self.write(")");
28692        Ok(())
28693    }
28694
28695    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
28696        // DATETIME_TRUNC(this, unit, zone)
28697        self.write_keyword("DATETIME_TRUNC");
28698        self.write("(");
28699        self.generate_expression(&e.this)?;
28700        self.write(", ");
28701        self.write_keyword(&e.unit);
28702        if let Some(zone) = &e.zone {
28703            self.write(", ");
28704            self.generate_expression(zone)?;
28705        }
28706        self.write(")");
28707        Ok(())
28708    }
28709
28710    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
28711        // DAYNAME(this)
28712        self.write_keyword("DAYNAME");
28713        self.write("(");
28714        self.generate_expression(&e.this)?;
28715        self.write(")");
28716        Ok(())
28717    }
28718
28719    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
28720        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
28721        self.write_keyword("DECLARE");
28722        self.write_space();
28723        if e.replace {
28724            self.write_keyword("OR");
28725            self.write_space();
28726            self.write_keyword("REPLACE");
28727            self.write_space();
28728        }
28729        for (i, expr) in e.expressions.iter().enumerate() {
28730            if i > 0 {
28731                self.write(", ");
28732            }
28733            self.generate_expression(expr)?;
28734        }
28735        Ok(())
28736    }
28737
28738    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
28739        use crate::dialects::DialectType;
28740
28741        // variable TYPE [DEFAULT default]
28742        self.generate_expression(&e.this)?;
28743        // BigQuery multi-variable: DECLARE X, Y, Z INT64
28744        for name in &e.additional_names {
28745            self.write(", ");
28746            self.generate_expression(name)?;
28747        }
28748        if let Some(kind) = &e.kind {
28749            self.write_space();
28750            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
28751            // TSQL: Always includes AS (normalization)
28752            // Others: Include AS if present in original
28753            match self.config.dialect {
28754                Some(DialectType::BigQuery) => {
28755                    self.write(kind);
28756                }
28757                Some(DialectType::TSQL) => {
28758                    // TSQL DECLARE: no AS keyword (sqlglot convention)
28759                    // Normalize INT to INTEGER for simple declarations
28760                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
28761                    let is_complex_table = kind.starts_with("TABLE")
28762                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
28763                    if is_complex_table {
28764                        self.write(kind);
28765                    } else if kind == "INT" {
28766                        self.write("INTEGER");
28767                    } else if kind.starts_with("TABLE") {
28768                        // Normalize INT to INTEGER inside simple TABLE column definitions
28769                        let normalized = kind
28770                            .replace(" INT ", " INTEGER ")
28771                            .replace(" INT,", " INTEGER,")
28772                            .replace(" INT)", " INTEGER)")
28773                            .replace("(INT ", "(INTEGER ");
28774                        self.write(&normalized);
28775                    } else {
28776                        self.write(kind);
28777                    }
28778                }
28779                _ => {
28780                    if e.has_as {
28781                        self.write_keyword("AS");
28782                        self.write_space();
28783                    }
28784                    self.write(kind);
28785                }
28786            }
28787        }
28788        if let Some(default) = &e.default {
28789            // BigQuery uses DEFAULT, others use =
28790            match self.config.dialect {
28791                Some(DialectType::BigQuery) => {
28792                    self.write_space();
28793                    self.write_keyword("DEFAULT");
28794                    self.write_space();
28795                }
28796                _ => {
28797                    self.write(" = ");
28798                }
28799            }
28800            self.generate_expression(default)?;
28801        }
28802        Ok(())
28803    }
28804
28805    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
28806        // DECODE(expr, search1, result1, search2, result2, ..., default)
28807        self.write_keyword("DECODE");
28808        self.write("(");
28809        for (i, expr) in e.expressions.iter().enumerate() {
28810            if i > 0 {
28811                self.write(", ");
28812            }
28813            self.generate_expression(expr)?;
28814        }
28815        self.write(")");
28816        Ok(())
28817    }
28818
28819    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> 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_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
28831        // DECOMPRESS(expr, 'method')
28832        self.write_keyword("DECOMPRESS");
28833        self.write("(");
28834        self.generate_expression(&e.this)?;
28835        self.write(", '");
28836        self.write(&e.method);
28837        self.write("')");
28838        Ok(())
28839    }
28840
28841    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
28842        // DECRYPT(value, passphrase [, aad [, algorithm]])
28843        self.write_keyword("DECRYPT");
28844        self.write("(");
28845        self.generate_expression(&e.this)?;
28846        if let Some(passphrase) = &e.passphrase {
28847            self.write(", ");
28848            self.generate_expression(passphrase)?;
28849        }
28850        if let Some(aad) = &e.aad {
28851            self.write(", ");
28852            self.generate_expression(aad)?;
28853        }
28854        if let Some(method) = &e.encryption_method {
28855            self.write(", ");
28856            self.generate_expression(method)?;
28857        }
28858        self.write(")");
28859        Ok(())
28860    }
28861
28862    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
28863        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
28864        self.write_keyword("DECRYPT_RAW");
28865        self.write("(");
28866        self.generate_expression(&e.this)?;
28867        if let Some(key) = &e.key {
28868            self.write(", ");
28869            self.generate_expression(key)?;
28870        }
28871        if let Some(iv) = &e.iv {
28872            self.write(", ");
28873            self.generate_expression(iv)?;
28874        }
28875        if let Some(aad) = &e.aad {
28876            self.write(", ");
28877            self.generate_expression(aad)?;
28878        }
28879        if let Some(method) = &e.encryption_method {
28880            self.write(", ");
28881            self.generate_expression(method)?;
28882        }
28883        self.write(")");
28884        Ok(())
28885    }
28886
28887    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
28888        // DEFINER = user
28889        self.write_keyword("DEFINER");
28890        self.write(" = ");
28891        self.generate_expression(&e.this)?;
28892        Ok(())
28893    }
28894
28895    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
28896        // Python: DETACH[DATABASE IF EXISTS] this
28897        self.write_keyword("DETACH");
28898        if e.exists {
28899            self.write_keyword(" DATABASE IF EXISTS");
28900        }
28901        self.write_space();
28902        self.generate_expression(&e.this)?;
28903        Ok(())
28904    }
28905
28906    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
28907        let property_name = match e.this.as_ref() {
28908            Expression::Identifier(id) => id.name.as_str(),
28909            Expression::Var(v) => v.this.as_str(),
28910            _ => "DICTIONARY",
28911        };
28912        self.write_keyword(property_name);
28913        self.write("(");
28914        self.write(&e.kind);
28915        if let Some(settings) = &e.settings {
28916            self.write("(");
28917            if let Expression::Tuple(t) = settings.as_ref() {
28918                if self.config.pretty && !t.expressions.is_empty() {
28919                    self.write_newline();
28920                    self.indent_level += 1;
28921                    for (i, pair) in t.expressions.iter().enumerate() {
28922                        if i > 0 {
28923                            self.write(",");
28924                            self.write_newline();
28925                        }
28926                        self.write_indent();
28927                        if let Expression::Tuple(pair_tuple) = pair {
28928                            if let Some(k) = pair_tuple.expressions.first() {
28929                                self.generate_expression(k)?;
28930                            }
28931                            if let Some(v) = pair_tuple.expressions.get(1) {
28932                                self.write(" ");
28933                                self.generate_expression(v)?;
28934                            }
28935                        } else {
28936                            self.generate_expression(pair)?;
28937                        }
28938                    }
28939                    self.indent_level -= 1;
28940                    self.write_newline();
28941                    self.write_indent();
28942                } else {
28943                    for (i, pair) in t.expressions.iter().enumerate() {
28944                        if i > 0 {
28945                            // ClickHouse dict properties are space-separated, not comma-separated
28946                            self.write(" ");
28947                        }
28948                        if let Expression::Tuple(pair_tuple) = pair {
28949                            if let Some(k) = pair_tuple.expressions.first() {
28950                                self.generate_expression(k)?;
28951                            }
28952                            if let Some(v) = pair_tuple.expressions.get(1) {
28953                                self.write(" ");
28954                                self.generate_expression(v)?;
28955                            }
28956                        } else {
28957                            self.generate_expression(pair)?;
28958                        }
28959                    }
28960                }
28961            } else {
28962                self.generate_expression(settings)?;
28963            }
28964            self.write(")");
28965        } else {
28966            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
28967            self.write("()");
28968        }
28969        self.write(")");
28970        Ok(())
28971    }
28972
28973    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
28974        let property_name = match e.this.as_ref() {
28975            Expression::Identifier(id) => id.name.as_str(),
28976            Expression::Var(v) => v.this.as_str(),
28977            _ => "RANGE",
28978        };
28979        self.write_keyword(property_name);
28980        self.write("(");
28981        if let Some(min) = &e.min {
28982            self.write_keyword("MIN");
28983            self.write_space();
28984            self.generate_expression(min)?;
28985        }
28986        if let Some(max) = &e.max {
28987            self.write_space();
28988            self.write_keyword("MAX");
28989            self.write_space();
28990            self.generate_expression(max)?;
28991        }
28992        self.write(")");
28993        Ok(())
28994    }
28995
28996    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
28997        // Python: {local}DIRECTORY {this}{row_format}
28998        if e.local.is_some() {
28999            self.write_keyword("LOCAL ");
29000        }
29001        self.write_keyword("DIRECTORY");
29002        self.write_space();
29003        self.generate_expression(&e.this)?;
29004        if let Some(row_format) = &e.row_format {
29005            self.write_space();
29006            self.generate_expression(row_format)?;
29007        }
29008        Ok(())
29009    }
29010
29011    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
29012        // Redshift: DISTKEY(column)
29013        self.write_keyword("DISTKEY");
29014        self.write("(");
29015        self.generate_expression(&e.this)?;
29016        self.write(")");
29017        Ok(())
29018    }
29019
29020    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
29021        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
29022        self.write_keyword("DISTSTYLE");
29023        self.write_space();
29024        self.generate_expression(&e.this)?;
29025        Ok(())
29026    }
29027
29028    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
29029        // Python: "DISTRIBUTE BY" expressions
29030        self.write_keyword("DISTRIBUTE BY");
29031        self.write_space();
29032        for (i, expr) in e.expressions.iter().enumerate() {
29033            if i > 0 {
29034                self.write(", ");
29035            }
29036            self.generate_expression(expr)?;
29037        }
29038        Ok(())
29039    }
29040
29041    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
29042        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
29043        self.write_keyword("DISTRIBUTED BY");
29044        self.write_space();
29045        self.write(&e.kind);
29046        if !e.expressions.is_empty() {
29047            self.write(" (");
29048            for (i, expr) in e.expressions.iter().enumerate() {
29049                if i > 0 {
29050                    self.write(", ");
29051                }
29052                self.generate_expression(expr)?;
29053            }
29054            self.write(")");
29055        }
29056        if let Some(buckets) = &e.buckets {
29057            self.write_space();
29058            self.write_keyword("BUCKETS");
29059            self.write_space();
29060            self.generate_expression(buckets)?;
29061        }
29062        if let Some(order) = &e.order {
29063            self.write_space();
29064            self.generate_expression(order)?;
29065        }
29066        Ok(())
29067    }
29068
29069    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
29070        // DOT_PRODUCT(vector1, vector2)
29071        self.write_keyword("DOT_PRODUCT");
29072        self.write("(");
29073        self.generate_expression(&e.this)?;
29074        self.write(", ");
29075        self.generate_expression(&e.expression)?;
29076        self.write(")");
29077        Ok(())
29078    }
29079
29080    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
29081        // Python: DROP{IF EXISTS }expressions
29082        self.write_keyword("DROP");
29083        if e.exists {
29084            self.write_keyword(" IF EXISTS ");
29085        } else {
29086            self.write_space();
29087        }
29088        for (i, expr) in e.expressions.iter().enumerate() {
29089            if i > 0 {
29090                self.write(", ");
29091            }
29092            self.generate_expression(expr)?;
29093        }
29094        Ok(())
29095    }
29096
29097    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
29098        // Python: DUPLICATE KEY (expressions)
29099        self.write_keyword("DUPLICATE KEY");
29100        self.write(" (");
29101        for (i, expr) in e.expressions.iter().enumerate() {
29102            if i > 0 {
29103                self.write(", ");
29104            }
29105            self.generate_expression(expr)?;
29106        }
29107        self.write(")");
29108        Ok(())
29109    }
29110
29111    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
29112        // ELT(index, str1, str2, ...)
29113        self.write_keyword("ELT");
29114        self.write("(");
29115        self.generate_expression(&e.this)?;
29116        for expr in &e.expressions {
29117            self.write(", ");
29118            self.generate_expression(expr)?;
29119        }
29120        self.write(")");
29121        Ok(())
29122    }
29123
29124    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
29125        // ENCODE(string, charset)
29126        self.write_keyword("ENCODE");
29127        self.write("(");
29128        self.generate_expression(&e.this)?;
29129        if let Some(charset) = &e.charset {
29130            self.write(", ");
29131            self.generate_expression(charset)?;
29132        }
29133        self.write(")");
29134        Ok(())
29135    }
29136
29137    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
29138        // Python: [KEY ]ENCODE this [properties]
29139        if e.key.is_some() {
29140            self.write_keyword("KEY ");
29141        }
29142        self.write_keyword("ENCODE");
29143        self.write_space();
29144        self.generate_expression(&e.this)?;
29145        if !e.properties.is_empty() {
29146            self.write(" (");
29147            for (i, prop) in e.properties.iter().enumerate() {
29148                if i > 0 {
29149                    self.write(", ");
29150                }
29151                self.generate_expression(prop)?;
29152            }
29153            self.write(")");
29154        }
29155        Ok(())
29156    }
29157
29158    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
29159        // ENCRYPT(value, passphrase [, aad [, algorithm]])
29160        self.write_keyword("ENCRYPT");
29161        self.write("(");
29162        self.generate_expression(&e.this)?;
29163        if let Some(passphrase) = &e.passphrase {
29164            self.write(", ");
29165            self.generate_expression(passphrase)?;
29166        }
29167        if let Some(aad) = &e.aad {
29168            self.write(", ");
29169            self.generate_expression(aad)?;
29170        }
29171        if let Some(method) = &e.encryption_method {
29172            self.write(", ");
29173            self.generate_expression(method)?;
29174        }
29175        self.write(")");
29176        Ok(())
29177    }
29178
29179    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
29180        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
29181        self.write_keyword("ENCRYPT_RAW");
29182        self.write("(");
29183        self.generate_expression(&e.this)?;
29184        if let Some(key) = &e.key {
29185            self.write(", ");
29186            self.generate_expression(key)?;
29187        }
29188        if let Some(iv) = &e.iv {
29189            self.write(", ");
29190            self.generate_expression(iv)?;
29191        }
29192        if let Some(aad) = &e.aad {
29193            self.write(", ");
29194            self.generate_expression(aad)?;
29195        }
29196        if let Some(method) = &e.encryption_method {
29197            self.write(", ");
29198            self.generate_expression(method)?;
29199        }
29200        self.write(")");
29201        Ok(())
29202    }
29203
29204    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
29205        // MySQL: ENGINE = InnoDB
29206        self.write_keyword("ENGINE");
29207        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
29208            self.write("=");
29209        } else {
29210            self.write(" = ");
29211        }
29212        self.generate_expression(&e.this)?;
29213        Ok(())
29214    }
29215
29216    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
29217        // ENVIRONMENT (expressions)
29218        self.write_keyword("ENVIRONMENT");
29219        self.write(" (");
29220        for (i, expr) in e.expressions.iter().enumerate() {
29221            if i > 0 {
29222                self.write(", ");
29223            }
29224            self.generate_expression(expr)?;
29225        }
29226        self.write(")");
29227        Ok(())
29228    }
29229
29230    fn generate_ephemeral_column_constraint(
29231        &mut self,
29232        e: &EphemeralColumnConstraint,
29233    ) -> Result<()> {
29234        // MySQL: EPHEMERAL [expr]
29235        self.write_keyword("EPHEMERAL");
29236        if let Some(this) = &e.this {
29237            self.write_space();
29238            self.generate_expression(this)?;
29239        }
29240        Ok(())
29241    }
29242
29243    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
29244        // Snowflake: EQUAL_NULL(a, b)
29245        self.write_keyword("EQUAL_NULL");
29246        self.write("(");
29247        self.generate_expression(&e.this)?;
29248        self.write(", ");
29249        self.generate_expression(&e.expression)?;
29250        self.write(")");
29251        Ok(())
29252    }
29253
29254    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
29255        use crate::dialects::DialectType;
29256
29257        // PostgreSQL uses <-> operator syntax
29258        match self.config.dialect {
29259            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
29260                self.generate_expression(&e.this)?;
29261                self.write(" <-> ");
29262                self.generate_expression(&e.expression)?;
29263            }
29264            _ => {
29265                // Other dialects use EUCLIDEAN_DISTANCE function
29266                self.write_keyword("EUCLIDEAN_DISTANCE");
29267                self.write("(");
29268                self.generate_expression(&e.this)?;
29269                self.write(", ");
29270                self.generate_expression(&e.expression)?;
29271                self.write(")");
29272            }
29273        }
29274        Ok(())
29275    }
29276
29277    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
29278        // EXECUTE AS CALLER|OWNER|user
29279        self.write_keyword("EXECUTE AS");
29280        self.write_space();
29281        self.generate_expression(&e.this)?;
29282        Ok(())
29283    }
29284
29285    fn generate_export(&mut self, e: &Export) -> Result<()> {
29286        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
29287        self.write_keyword("EXPORT DATA");
29288        if let Some(connection) = &e.connection {
29289            self.write_space();
29290            self.write_keyword("WITH CONNECTION");
29291            self.write_space();
29292            self.generate_expression(connection)?;
29293        }
29294        if !e.options.is_empty() {
29295            self.write_space();
29296            self.generate_options_clause(&e.options)?;
29297        }
29298        self.write_space();
29299        self.write_keyword("AS");
29300        self.write_space();
29301        self.generate_expression(&e.this)?;
29302        Ok(())
29303    }
29304
29305    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
29306        // EXTERNAL [this]
29307        self.write_keyword("EXTERNAL");
29308        if let Some(this) = &e.this {
29309            self.write_space();
29310            self.generate_expression(this)?;
29311        }
29312        Ok(())
29313    }
29314
29315    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
29316        // Python: {no}FALLBACK{protection}
29317        if e.no.is_some() {
29318            self.write_keyword("NO ");
29319        }
29320        self.write_keyword("FALLBACK");
29321        if e.protection.is_some() {
29322            self.write_keyword(" PROTECTION");
29323        }
29324        Ok(())
29325    }
29326
29327    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
29328        // BigQuery: FARM_FINGERPRINT(value)
29329        self.write_keyword("FARM_FINGERPRINT");
29330        self.write("(");
29331        for (i, expr) in e.expressions.iter().enumerate() {
29332            if i > 0 {
29333                self.write(", ");
29334            }
29335            self.generate_expression(expr)?;
29336        }
29337        self.write(")");
29338        Ok(())
29339    }
29340
29341    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
29342        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
29343        self.write_keyword("FEATURES_AT_TIME");
29344        self.write("(");
29345        self.generate_expression(&e.this)?;
29346        if let Some(time) = &e.time {
29347            self.write(", ");
29348            self.generate_expression(time)?;
29349        }
29350        if let Some(num_rows) = &e.num_rows {
29351            self.write(", ");
29352            self.generate_expression(num_rows)?;
29353        }
29354        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
29355            self.write(", ");
29356            self.generate_expression(ignore_nulls)?;
29357        }
29358        self.write(")");
29359        Ok(())
29360    }
29361
29362    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
29363        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
29364        let use_limit = !e.percent
29365            && !e.with_ties
29366            && e.count.is_some()
29367            && matches!(
29368                self.config.dialect,
29369                Some(DialectType::Spark)
29370                    | Some(DialectType::Hive)
29371                    | Some(DialectType::DuckDB)
29372                    | Some(DialectType::SQLite)
29373                    | Some(DialectType::MySQL)
29374                    | Some(DialectType::BigQuery)
29375                    | Some(DialectType::Databricks)
29376                    | Some(DialectType::StarRocks)
29377                    | Some(DialectType::Doris)
29378                    | Some(DialectType::Athena)
29379                    | Some(DialectType::ClickHouse)
29380            );
29381
29382        if use_limit {
29383            self.write_keyword("LIMIT");
29384            self.write_space();
29385            self.generate_expression(e.count.as_ref().unwrap())?;
29386            return Ok(());
29387        }
29388
29389        // Python: FETCH direction count limit_options
29390        self.write_keyword("FETCH");
29391        if !e.direction.is_empty() {
29392            self.write_space();
29393            self.write_keyword(&e.direction);
29394        }
29395        if let Some(count) = &e.count {
29396            self.write_space();
29397            self.generate_expression(count)?;
29398        }
29399        // Generate PERCENT, ROWS, WITH TIES/ONLY
29400        if e.percent {
29401            self.write_keyword(" PERCENT");
29402        }
29403        if e.rows {
29404            self.write_keyword(" ROWS");
29405        }
29406        if e.with_ties {
29407            self.write_keyword(" WITH TIES");
29408        } else if e.rows {
29409            self.write_keyword(" ONLY");
29410        } else {
29411            self.write_keyword(" ROWS ONLY");
29412        }
29413        Ok(())
29414    }
29415
29416    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
29417        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
29418        // For Spark/Databricks without hive_format: USING this
29419        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
29420        if e.hive_format.is_some() {
29421            // Hive format: STORED AS ...
29422            self.write_keyword("STORED AS");
29423            self.write_space();
29424            if let Some(this) = &e.this {
29425                // Uppercase the format name (e.g., parquet -> PARQUET)
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!(self.config.dialect, Some(DialectType::Hive)) {
29433            // Hive: STORED AS format
29434            self.write_keyword("STORED AS");
29435            self.write_space();
29436            if let Some(this) = &e.this {
29437                if let Expression::Identifier(id) = this.as_ref() {
29438                    self.write_keyword(&id.name.to_ascii_uppercase());
29439                } else {
29440                    self.generate_expression(this)?;
29441                }
29442            }
29443        } else if matches!(
29444            self.config.dialect,
29445            Some(DialectType::Spark) | Some(DialectType::Databricks)
29446        ) {
29447            // Spark/Databricks: USING format (e.g., USING DELTA)
29448            self.write_keyword("USING");
29449            self.write_space();
29450            if let Some(this) = &e.this {
29451                self.generate_expression(this)?;
29452            }
29453        } else {
29454            // Snowflake/standard format
29455            self.write_keyword("FILE_FORMAT");
29456            self.write(" = ");
29457            if let Some(this) = &e.this {
29458                self.generate_expression(this)?;
29459            } else if !e.expressions.is_empty() {
29460                self.write("(");
29461                for (i, expr) in e.expressions.iter().enumerate() {
29462                    if i > 0 {
29463                        self.write(", ");
29464                    }
29465                    self.generate_expression(expr)?;
29466                }
29467                self.write(")");
29468            }
29469        }
29470        Ok(())
29471    }
29472
29473    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
29474        // agg_func FILTER(WHERE condition)
29475        self.generate_expression(&e.this)?;
29476        self.write_space();
29477        self.write_keyword("FILTER");
29478        self.write("(");
29479        self.write_keyword("WHERE");
29480        self.write_space();
29481        self.generate_expression(&e.expression)?;
29482        self.write(")");
29483        Ok(())
29484    }
29485
29486    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
29487        // FLOAT64(this) or FLOAT64(this, expression)
29488        self.write_keyword("FLOAT64");
29489        self.write("(");
29490        self.generate_expression(&e.this)?;
29491        if let Some(expr) = &e.expression {
29492            self.write(", ");
29493            self.generate_expression(expr)?;
29494        }
29495        self.write(")");
29496        Ok(())
29497    }
29498
29499    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
29500        // FOR this DO expression
29501        self.write_keyword("FOR");
29502        self.write_space();
29503        self.generate_expression(&e.this)?;
29504        self.write_space();
29505        self.write_keyword("DO");
29506        self.write_space();
29507        self.generate_expression(&e.expression)?;
29508        Ok(())
29509    }
29510
29511    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
29512        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
29513        self.write_keyword("FOREIGN KEY");
29514        if !e.expressions.is_empty() {
29515            self.write(" (");
29516            for (i, expr) in e.expressions.iter().enumerate() {
29517                if i > 0 {
29518                    self.write(", ");
29519                }
29520                self.generate_expression(expr)?;
29521            }
29522            self.write(")");
29523        }
29524        if let Some(reference) = &e.reference {
29525            self.write_space();
29526            self.generate_expression(reference)?;
29527        }
29528        if let Some(delete) = &e.delete {
29529            self.write_space();
29530            self.write_keyword("ON DELETE");
29531            self.write_space();
29532            self.generate_expression(delete)?;
29533        }
29534        if let Some(update) = &e.update {
29535            self.write_space();
29536            self.write_keyword("ON UPDATE");
29537            self.write_space();
29538            self.generate_expression(update)?;
29539        }
29540        if !e.options.is_empty() {
29541            self.write_space();
29542            for (i, opt) in e.options.iter().enumerate() {
29543                if i > 0 {
29544                    self.write_space();
29545                }
29546                self.generate_expression(opt)?;
29547            }
29548        }
29549        Ok(())
29550    }
29551
29552    fn generate_format(&mut self, e: &Format) -> Result<()> {
29553        // FORMAT(this, expressions...)
29554        self.write_keyword("FORMAT");
29555        self.write("(");
29556        self.generate_expression(&e.this)?;
29557        for expr in &e.expressions {
29558            self.write(", ");
29559            self.generate_expression(expr)?;
29560        }
29561        self.write(")");
29562        Ok(())
29563    }
29564
29565    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
29566        // Teradata: column (FORMAT 'format_string')
29567        self.generate_expression(&e.this)?;
29568        self.write(" (");
29569        self.write_keyword("FORMAT");
29570        self.write(" '");
29571        self.write(&e.format);
29572        self.write("')");
29573        Ok(())
29574    }
29575
29576    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
29577        // Python: FREESPACE=this[PERCENT]
29578        self.write_keyword("FREESPACE");
29579        self.write("=");
29580        self.generate_expression(&e.this)?;
29581        if e.percent.is_some() {
29582            self.write_keyword(" PERCENT");
29583        }
29584        Ok(())
29585    }
29586
29587    fn generate_from(&mut self, e: &From) -> Result<()> {
29588        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
29589        self.write_keyword("FROM");
29590        self.write_space();
29591
29592        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
29593        // But keep commas when TABLESAMPLE is present
29594        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
29595        use crate::dialects::DialectType;
29596        let has_tablesample = e
29597            .expressions
29598            .iter()
29599            .any(|expr| matches!(expr, Expression::TableSample(_)));
29600        let is_cross_join_dialect = matches!(
29601            self.config.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 source_is_same_as_target2 = self.config.source_dialect.is_some()
29610            && self.config.source_dialect == self.config.dialect;
29611        let source_is_cross_join_dialect2 = matches!(
29612            self.config.source_dialect,
29613            Some(DialectType::BigQuery)
29614                | Some(DialectType::Hive)
29615                | Some(DialectType::Spark)
29616                | Some(DialectType::Databricks)
29617                | Some(DialectType::SQLite)
29618                | Some(DialectType::ClickHouse)
29619        );
29620        let use_cross_join = !has_tablesample
29621            && is_cross_join_dialect
29622            && (source_is_same_as_target2
29623                || source_is_cross_join_dialect2
29624                || self.config.source_dialect.is_none());
29625
29626        // Snowflake wraps standalone VALUES in FROM clause with parentheses
29627        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
29628
29629        for (i, expr) in e.expressions.iter().enumerate() {
29630            if i > 0 {
29631                if use_cross_join {
29632                    self.write(" CROSS JOIN ");
29633                } else {
29634                    self.write(", ");
29635                }
29636            }
29637            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
29638                self.write("(");
29639                self.generate_expression(expr)?;
29640                self.write(")");
29641            } else {
29642                self.generate_expression(expr)?;
29643            }
29644            // Output leading comments that were on the table name before FROM
29645            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
29646            let leading = Self::extract_table_leading_comments(expr);
29647            for comment in &leading {
29648                self.write_space();
29649                self.write_formatted_comment(comment);
29650            }
29651        }
29652        Ok(())
29653    }
29654
29655    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
29656    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
29657        match expr {
29658            Expression::Table(t) => t.leading_comments.clone(),
29659            Expression::Pivot(p) => {
29660                if let Expression::Table(t) = &p.this {
29661                    t.leading_comments.clone()
29662                } else {
29663                    Vec::new()
29664                }
29665            }
29666            _ => Vec::new(),
29667        }
29668    }
29669
29670    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
29671        // FROM_BASE(this, expression) - convert from base N
29672        self.write_keyword("FROM_BASE");
29673        self.write("(");
29674        self.generate_expression(&e.this)?;
29675        self.write(", ");
29676        self.generate_expression(&e.expression)?;
29677        self.write(")");
29678        Ok(())
29679    }
29680
29681    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
29682        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
29683        self.generate_expression(&e.this)?;
29684        if let Some(zone) = &e.zone {
29685            self.write_space();
29686            self.write_keyword("AT TIME ZONE");
29687            self.write_space();
29688            self.generate_expression(zone)?;
29689            self.write_space();
29690            self.write_keyword("AT TIME ZONE");
29691            self.write(" 'UTC'");
29692        }
29693        Ok(())
29694    }
29695
29696    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
29697        // GAP_FILL(this, ts_column, bucket_width, ...)
29698        self.write_keyword("GAP_FILL");
29699        self.write("(");
29700        self.generate_expression(&e.this)?;
29701        if let Some(ts_column) = &e.ts_column {
29702            self.write(", ");
29703            self.generate_expression(ts_column)?;
29704        }
29705        if let Some(bucket_width) = &e.bucket_width {
29706            self.write(", ");
29707            self.generate_expression(bucket_width)?;
29708        }
29709        if let Some(partitioning_columns) = &e.partitioning_columns {
29710            self.write(", ");
29711            self.generate_expression(partitioning_columns)?;
29712        }
29713        if let Some(value_columns) = &e.value_columns {
29714            self.write(", ");
29715            self.generate_expression(value_columns)?;
29716        }
29717        self.write(")");
29718        Ok(())
29719    }
29720
29721    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
29722        // GENERATE_DATE_ARRAY(start, end, step)
29723        self.write_keyword("GENERATE_DATE_ARRAY");
29724        self.write("(");
29725        let mut first = true;
29726        if let Some(start) = &e.start {
29727            self.generate_expression(start)?;
29728            first = false;
29729        }
29730        if let Some(end) = &e.end {
29731            if !first {
29732                self.write(", ");
29733            }
29734            self.generate_expression(end)?;
29735            first = false;
29736        }
29737        if let Some(step) = &e.step {
29738            if !first {
29739                self.write(", ");
29740            }
29741            self.generate_expression(step)?;
29742        }
29743        self.write(")");
29744        Ok(())
29745    }
29746
29747    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
29748        // ML.GENERATE_EMBEDDING(model, content, params)
29749        self.write_keyword("ML.GENERATE_EMBEDDING");
29750        self.write("(");
29751        self.generate_expression(&e.this)?;
29752        self.write(", ");
29753        self.generate_expression(&e.expression)?;
29754        if let Some(params) = &e.params_struct {
29755            self.write(", ");
29756            self.generate_expression(params)?;
29757        }
29758        self.write(")");
29759        Ok(())
29760    }
29761
29762    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
29763        // Dialect-specific function name
29764        let fn_name = match self.config.dialect {
29765            Some(DialectType::Presto)
29766            | Some(DialectType::Trino)
29767            | Some(DialectType::Athena)
29768            | Some(DialectType::Spark)
29769            | Some(DialectType::Databricks)
29770            | Some(DialectType::Hive) => "SEQUENCE",
29771            _ => "GENERATE_SERIES",
29772        };
29773        self.write_keyword(fn_name);
29774        self.write("(");
29775        let mut first = true;
29776        if let Some(start) = &e.start {
29777            self.generate_expression(start)?;
29778            first = false;
29779        }
29780        if let Some(end) = &e.end {
29781            if !first {
29782                self.write(", ");
29783            }
29784            self.generate_expression(end)?;
29785            first = false;
29786        }
29787        if let Some(step) = &e.step {
29788            if !first {
29789                self.write(", ");
29790            }
29791            // For Presto/Trino: convert WEEK intervals to DAY multiples
29792            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
29793            if matches!(
29794                self.config.dialect,
29795                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
29796            ) {
29797                if let Some(converted) = self.convert_week_interval_to_day(step) {
29798                    self.generate_expression(&converted)?;
29799                } else {
29800                    self.generate_expression(step)?;
29801                }
29802            } else {
29803                self.generate_expression(step)?;
29804            }
29805        }
29806        self.write(")");
29807        Ok(())
29808    }
29809
29810    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
29811    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
29812    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
29813        use crate::expressions::*;
29814        if let Expression::Interval(ref iv) = expr {
29815            // Check for structured WEEK unit
29816            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
29817                unit: IntervalUnit::Week,
29818                ..
29819            }) = &iv.unit
29820            {
29821                // Value is in iv.this
29822                let count = match &iv.this {
29823                    Some(Expression::Literal(lit)) => match lit.as_ref() {
29824                        Literal::String(s) | Literal::Number(s) => s.clone(),
29825                        _ => return None,
29826                    },
29827                    _ => return None,
29828                };
29829                (true, count)
29830            } else if iv.unit.is_none() {
29831                // Check for string-encoded interval like "1 WEEK"
29832                if let Some(Expression::Literal(lit)) = &iv.this {
29833                    if let Literal::String(s) = lit.as_ref() {
29834                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
29835                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
29836                            (true, parts[0].to_string())
29837                        } else {
29838                            (false, String::new())
29839                        }
29840                    } else {
29841                        (false, String::new())
29842                    }
29843                } else {
29844                    (false, String::new())
29845                }
29846            } else {
29847                (false, String::new())
29848            };
29849
29850            if is_week {
29851                // Build: (N * INTERVAL '7' DAY)
29852                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
29853                let day_interval = Expression::Interval(Box::new(Interval {
29854                    this: Some(Expression::Literal(Box::new(Literal::String(
29855                        "7".to_string(),
29856                    )))),
29857                    unit: Some(IntervalUnitSpec::Simple {
29858                        unit: IntervalUnit::Day,
29859                        use_plural: false,
29860                    }),
29861                }));
29862                let mul = Expression::Mul(Box::new(BinaryOp {
29863                    left: count_expr,
29864                    right: day_interval,
29865                    left_comments: vec![],
29866                    operator_comments: vec![],
29867                    trailing_comments: vec![],
29868                    inferred_type: None,
29869                }));
29870                return Some(Expression::Paren(Box::new(Paren {
29871                    this: mul,
29872                    trailing_comments: vec![],
29873                })));
29874            }
29875        }
29876        None
29877    }
29878
29879    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
29880        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
29881        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
29882        self.write("(");
29883        let mut first = true;
29884        if let Some(start) = &e.start {
29885            self.generate_expression(start)?;
29886            first = false;
29887        }
29888        if let Some(end) = &e.end {
29889            if !first {
29890                self.write(", ");
29891            }
29892            self.generate_expression(end)?;
29893            first = false;
29894        }
29895        if let Some(step) = &e.step {
29896            if !first {
29897                self.write(", ");
29898            }
29899            self.generate_expression(step)?;
29900        }
29901        self.write(")");
29902        Ok(())
29903    }
29904
29905    fn generate_generated_as_identity_column_constraint(
29906        &mut self,
29907        e: &GeneratedAsIdentityColumnConstraint,
29908    ) -> Result<()> {
29909        use crate::dialects::DialectType;
29910
29911        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
29912        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
29913            self.write_keyword("AUTOINCREMENT");
29914            if let Some(start) = &e.start {
29915                self.write_keyword(" START ");
29916                self.generate_expression(start)?;
29917            }
29918            if let Some(increment) = &e.increment {
29919                self.write_keyword(" INCREMENT ");
29920                self.generate_expression(increment)?;
29921            }
29922            return Ok(());
29923        }
29924
29925        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
29926        self.write_keyword("GENERATED");
29927        if let Some(this) = &e.this {
29928            // Check if it's a truthy boolean expression
29929            if let Expression::Boolean(b) = this.as_ref() {
29930                if b.value {
29931                    self.write_keyword(" ALWAYS");
29932                } else {
29933                    self.write_keyword(" BY DEFAULT");
29934                    if e.on_null.is_some() {
29935                        self.write_keyword(" ON NULL");
29936                    }
29937                }
29938            } else {
29939                self.write_keyword(" ALWAYS");
29940            }
29941        }
29942        self.write_keyword(" AS IDENTITY");
29943        // Add sequence options if any
29944        let has_options = e.start.is_some()
29945            || e.increment.is_some()
29946            || e.minvalue.is_some()
29947            || e.maxvalue.is_some();
29948        if has_options {
29949            self.write(" (");
29950            let mut first = true;
29951            if let Some(start) = &e.start {
29952                self.write_keyword("START WITH ");
29953                self.generate_expression(start)?;
29954                first = false;
29955            }
29956            if let Some(increment) = &e.increment {
29957                if !first {
29958                    self.write(" ");
29959                }
29960                self.write_keyword("INCREMENT BY ");
29961                self.generate_expression(increment)?;
29962                first = false;
29963            }
29964            if let Some(minvalue) = &e.minvalue {
29965                if !first {
29966                    self.write(" ");
29967                }
29968                self.write_keyword("MINVALUE ");
29969                self.generate_expression(minvalue)?;
29970                first = false;
29971            }
29972            if let Some(maxvalue) = &e.maxvalue {
29973                if !first {
29974                    self.write(" ");
29975                }
29976                self.write_keyword("MAXVALUE ");
29977                self.generate_expression(maxvalue)?;
29978            }
29979            self.write(")");
29980        }
29981        Ok(())
29982    }
29983
29984    fn generate_generated_as_row_column_constraint(
29985        &mut self,
29986        e: &GeneratedAsRowColumnConstraint,
29987    ) -> Result<()> {
29988        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
29989        self.write_keyword("GENERATED ALWAYS AS ROW ");
29990        if e.start.is_some() {
29991            self.write_keyword("START");
29992        } else {
29993            self.write_keyword("END");
29994        }
29995        if e.hidden.is_some() {
29996            self.write_keyword(" HIDDEN");
29997        }
29998        Ok(())
29999    }
30000
30001    fn generate_get(&mut self, e: &Get) -> Result<()> {
30002        // GET this target properties
30003        self.write_keyword("GET");
30004        self.write_space();
30005        self.generate_expression(&e.this)?;
30006        if let Some(target) = &e.target {
30007            self.write_space();
30008            self.generate_expression(target)?;
30009        }
30010        for prop in &e.properties {
30011            self.write_space();
30012            self.generate_expression(prop)?;
30013        }
30014        Ok(())
30015    }
30016
30017    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
30018        // GetExtract generates bracket access: this[expression]
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_getbit(&mut self, e: &Getbit) -> Result<()> {
30027        // GETBIT(this, expression) or GET_BIT(this, expression)
30028        self.write_keyword("GETBIT");
30029        self.write("(");
30030        self.generate_expression(&e.this)?;
30031        self.write(", ");
30032        self.generate_expression(&e.expression)?;
30033        self.write(")");
30034        Ok(())
30035    }
30036
30037    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
30038        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
30039        if e.is_role {
30040            self.write_keyword("ROLE");
30041            self.write_space();
30042        } else if e.is_group {
30043            self.write_keyword("GROUP");
30044            self.write_space();
30045        } else if e.is_share {
30046            self.write_keyword("SHARE");
30047            self.write_space();
30048        }
30049        self.write(&e.name.name);
30050        Ok(())
30051    }
30052
30053    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
30054        // privilege(columns) or just privilege
30055        self.generate_expression(&e.this)?;
30056        if !e.expressions.is_empty() {
30057            self.write("(");
30058            for (i, expr) in e.expressions.iter().enumerate() {
30059                if i > 0 {
30060                    self.write(", ");
30061                }
30062                self.generate_expression(expr)?;
30063            }
30064            self.write(")");
30065        }
30066        Ok(())
30067    }
30068
30069    fn generate_group(&mut self, e: &Group) -> Result<()> {
30070        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
30071        self.write_keyword("GROUP BY");
30072        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
30073        match e.all {
30074            Some(true) => {
30075                self.write_space();
30076                self.write_keyword("ALL");
30077            }
30078            Some(false) => {
30079                self.write_space();
30080                self.write_keyword("DISTINCT");
30081            }
30082            None => {}
30083        }
30084        if !e.expressions.is_empty() {
30085            self.write_space();
30086            for (i, expr) in e.expressions.iter().enumerate() {
30087                if i > 0 {
30088                    self.write(", ");
30089                }
30090                self.generate_expression(expr)?;
30091            }
30092        }
30093        // Handle CUBE, ROLLUP, GROUPING SETS
30094        if let Some(cube) = &e.cube {
30095            if !e.expressions.is_empty() {
30096                self.write(", ");
30097            } else {
30098                self.write_space();
30099            }
30100            self.generate_expression(cube)?;
30101        }
30102        if let Some(rollup) = &e.rollup {
30103            if !e.expressions.is_empty() || e.cube.is_some() {
30104                self.write(", ");
30105            } else {
30106                self.write_space();
30107            }
30108            self.generate_expression(rollup)?;
30109        }
30110        if let Some(grouping_sets) = &e.grouping_sets {
30111            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
30112                self.write(", ");
30113            } else {
30114                self.write_space();
30115            }
30116            self.generate_expression(grouping_sets)?;
30117        }
30118        if let Some(totals) = &e.totals {
30119            self.write_space();
30120            self.write_keyword("WITH TOTALS");
30121            self.generate_expression(totals)?;
30122        }
30123        Ok(())
30124    }
30125
30126    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
30127        // GROUP BY expressions
30128        self.write_keyword("GROUP BY");
30129        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
30130        match e.all {
30131            Some(true) => {
30132                self.write_space();
30133                self.write_keyword("ALL");
30134            }
30135            Some(false) => {
30136                self.write_space();
30137                self.write_keyword("DISTINCT");
30138            }
30139            None => {}
30140        }
30141
30142        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
30143        // These are represented as Cube/Rollup expressions with empty expressions at the end
30144        let mut trailing_cube = false;
30145        let mut trailing_rollup = false;
30146        let mut regular_expressions: Vec<&Expression> = Vec::new();
30147
30148        for expr in &e.expressions {
30149            match expr {
30150                Expression::Cube(c) if c.expressions.is_empty() => {
30151                    trailing_cube = true;
30152                }
30153                Expression::Rollup(r) if r.expressions.is_empty() => {
30154                    trailing_rollup = true;
30155                }
30156                _ => {
30157                    regular_expressions.push(expr);
30158                }
30159            }
30160        }
30161
30162        // In pretty mode, put columns on separate lines
30163        if self.config.pretty {
30164            self.write_newline();
30165            self.indent_level += 1;
30166            for (i, expr) in regular_expressions.iter().enumerate() {
30167                if i > 0 {
30168                    self.write(",");
30169                    self.write_newline();
30170                }
30171                self.write_indent();
30172                self.generate_expression(expr)?;
30173            }
30174            self.indent_level -= 1;
30175        } else {
30176            self.write_space();
30177            for (i, expr) in regular_expressions.iter().enumerate() {
30178                if i > 0 {
30179                    self.write(", ");
30180                }
30181                self.generate_expression(expr)?;
30182            }
30183        }
30184
30185        // Output trailing WITH CUBE or WITH ROLLUP
30186        if trailing_cube {
30187            self.write_space();
30188            self.write_keyword("WITH CUBE");
30189        } else if trailing_rollup {
30190            self.write_space();
30191            self.write_keyword("WITH ROLLUP");
30192        }
30193
30194        // ClickHouse: WITH TOTALS
30195        if e.totals {
30196            self.write_space();
30197            self.write_keyword("WITH TOTALS");
30198        }
30199
30200        Ok(())
30201    }
30202
30203    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
30204        // GROUPING(col1, col2, ...)
30205        self.write_keyword("GROUPING");
30206        self.write("(");
30207        for (i, expr) in e.expressions.iter().enumerate() {
30208            if i > 0 {
30209                self.write(", ");
30210            }
30211            self.generate_expression(expr)?;
30212        }
30213        self.write(")");
30214        Ok(())
30215    }
30216
30217    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
30218        // GROUPING_ID(col1, col2, ...)
30219        self.write_keyword("GROUPING_ID");
30220        self.write("(");
30221        for (i, expr) in e.expressions.iter().enumerate() {
30222            if i > 0 {
30223                self.write(", ");
30224            }
30225            self.generate_expression(expr)?;
30226        }
30227        self.write(")");
30228        Ok(())
30229    }
30230
30231    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
30232        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
30233        self.write_keyword("GROUPING SETS");
30234        self.write(" (");
30235        for (i, expr) in e.expressions.iter().enumerate() {
30236            if i > 0 {
30237                self.write(", ");
30238            }
30239            self.generate_expression(expr)?;
30240        }
30241        self.write(")");
30242        Ok(())
30243    }
30244
30245    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
30246        // HASH_AGG(this, expressions...)
30247        self.write_keyword("HASH_AGG");
30248        self.write("(");
30249        self.generate_expression(&e.this)?;
30250        for expr in &e.expressions {
30251            self.write(", ");
30252            self.generate_expression(expr)?;
30253        }
30254        self.write(")");
30255        Ok(())
30256    }
30257
30258    fn generate_having(&mut self, e: &Having) -> Result<()> {
30259        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
30260        self.write_keyword("HAVING");
30261        self.write_space();
30262        self.generate_expression(&e.this)?;
30263        Ok(())
30264    }
30265
30266    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
30267        // Python: this HAVING MAX|MIN expression
30268        self.generate_expression(&e.this)?;
30269        self.write_space();
30270        self.write_keyword("HAVING");
30271        self.write_space();
30272        if e.max.is_some() {
30273            self.write_keyword("MAX");
30274        } else {
30275            self.write_keyword("MIN");
30276        }
30277        self.write_space();
30278        self.generate_expression(&e.expression)?;
30279        Ok(())
30280    }
30281
30282    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
30283        use crate::dialects::DialectType;
30284        // DuckDB: convert dollar-tagged strings to single-quoted
30285        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
30286            // Extract the string content and output as single-quoted
30287            if let Expression::Literal(ref lit) = *e.this {
30288                if let Literal::String(ref s) = lit.as_ref() {
30289                    return self.generate_string_literal(s);
30290                }
30291            }
30292        }
30293        // PostgreSQL: preserve dollar-quoting
30294        if matches!(
30295            self.config.dialect,
30296            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
30297        ) {
30298            self.write("$");
30299            if let Some(tag) = &e.tag {
30300                self.generate_expression(tag)?;
30301            }
30302            self.write("$");
30303            self.generate_expression(&e.this)?;
30304            self.write("$");
30305            if let Some(tag) = &e.tag {
30306                self.generate_expression(tag)?;
30307            }
30308            self.write("$");
30309            return Ok(());
30310        }
30311        // Default: output as dollar-tagged
30312        self.write("$");
30313        if let Some(tag) = &e.tag {
30314            self.generate_expression(tag)?;
30315        }
30316        self.write("$");
30317        self.generate_expression(&e.this)?;
30318        self.write("$");
30319        if let Some(tag) = &e.tag {
30320            self.generate_expression(tag)?;
30321        }
30322        self.write("$");
30323        Ok(())
30324    }
30325
30326    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
30327        // HEX_ENCODE(this)
30328        self.write_keyword("HEX_ENCODE");
30329        self.write("(");
30330        self.generate_expression(&e.this)?;
30331        self.write(")");
30332        Ok(())
30333    }
30334
30335    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
30336        // Python: this (kind => expression)
30337        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
30338        match e.this.as_ref() {
30339            Expression::Identifier(id) => self.write(&id.name),
30340            other => self.generate_expression(other)?,
30341        }
30342        self.write(" (");
30343        self.write(&e.kind);
30344        self.write(" => ");
30345        self.generate_expression(&e.expression)?;
30346        self.write(")");
30347        Ok(())
30348    }
30349
30350    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
30351        // HLL(this, expressions...)
30352        self.write_keyword("HLL");
30353        self.write("(");
30354        self.generate_expression(&e.this)?;
30355        for expr in &e.expressions {
30356            self.write(", ");
30357            self.generate_expression(expr)?;
30358        }
30359        self.write(")");
30360        Ok(())
30361    }
30362
30363    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
30364        // Python: IN|OUT|IN OUT
30365        if e.input_.is_some() && e.output.is_some() {
30366            self.write_keyword("IN OUT");
30367        } else if e.input_.is_some() {
30368            self.write_keyword("IN");
30369        } else if e.output.is_some() {
30370            self.write_keyword("OUT");
30371        }
30372        Ok(())
30373    }
30374
30375    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
30376        // Python: INCLUDE this [column_def] [AS alias]
30377        self.write_keyword("INCLUDE");
30378        self.write_space();
30379        self.generate_expression(&e.this)?;
30380        if let Some(column_def) = &e.column_def {
30381            self.write_space();
30382            self.generate_expression(column_def)?;
30383        }
30384        if let Some(alias) = &e.alias {
30385            self.write_space();
30386            self.write_keyword("AS");
30387            self.write_space();
30388            self.write(alias);
30389        }
30390        Ok(())
30391    }
30392
30393    fn generate_index(&mut self, e: &Index) -> Result<()> {
30394        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
30395        if e.unique {
30396            self.write_keyword("UNIQUE");
30397            self.write_space();
30398        }
30399        if e.primary.is_some() {
30400            self.write_keyword("PRIMARY");
30401            self.write_space();
30402        }
30403        if e.amp.is_some() {
30404            self.write_keyword("AMP");
30405            self.write_space();
30406        }
30407        if e.table.is_none() {
30408            self.write_keyword("INDEX");
30409            self.write_space();
30410        }
30411        if let Some(name) = &e.this {
30412            self.generate_expression(name)?;
30413            self.write_space();
30414        }
30415        if let Some(table) = &e.table {
30416            self.write_keyword("ON");
30417            self.write_space();
30418            self.generate_expression(table)?;
30419        }
30420        if !e.params.is_empty() {
30421            self.write("(");
30422            for (i, param) in e.params.iter().enumerate() {
30423                if i > 0 {
30424                    self.write(", ");
30425                }
30426                self.generate_expression(param)?;
30427            }
30428            self.write(")");
30429        }
30430        Ok(())
30431    }
30432
30433    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
30434        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
30435        if let Some(kind) = &e.kind {
30436            self.write(kind);
30437            self.write_space();
30438        }
30439        self.write_keyword("INDEX");
30440        if let Some(this) = &e.this {
30441            self.write_space();
30442            self.generate_expression(this)?;
30443        }
30444        if let Some(index_type) = &e.index_type {
30445            self.write_space();
30446            self.write_keyword("USING");
30447            self.write_space();
30448            self.generate_expression(index_type)?;
30449        }
30450        if !e.expressions.is_empty() {
30451            self.write(" (");
30452            for (i, expr) in e.expressions.iter().enumerate() {
30453                if i > 0 {
30454                    self.write(", ");
30455                }
30456                self.generate_expression(expr)?;
30457            }
30458            self.write(")");
30459        }
30460        for opt in &e.options {
30461            self.write_space();
30462            self.generate_expression(opt)?;
30463        }
30464        Ok(())
30465    }
30466
30467    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
30468        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
30469        if let Some(key_block_size) = &e.key_block_size {
30470            self.write_keyword("KEY_BLOCK_SIZE");
30471            self.write(" = ");
30472            self.generate_expression(key_block_size)?;
30473        } else if let Some(using) = &e.using {
30474            self.write_keyword("USING");
30475            self.write_space();
30476            self.generate_expression(using)?;
30477        } else if let Some(parser) = &e.parser {
30478            self.write_keyword("WITH PARSER");
30479            self.write_space();
30480            self.generate_expression(parser)?;
30481        } else if let Some(comment) = &e.comment {
30482            self.write_keyword("COMMENT");
30483            self.write_space();
30484            self.generate_expression(comment)?;
30485        } else if let Some(visible) = &e.visible {
30486            self.generate_expression(visible)?;
30487        } else if let Some(engine_attr) = &e.engine_attr {
30488            self.write_keyword("ENGINE_ATTRIBUTE");
30489            self.write(" = ");
30490            self.generate_expression(engine_attr)?;
30491        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
30492            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
30493            self.write(" = ");
30494            self.generate_expression(secondary_engine_attr)?;
30495        }
30496        Ok(())
30497    }
30498
30499    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
30500        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
30501        if let Some(using) = &e.using {
30502            self.write_keyword("USING");
30503            self.write_space();
30504            self.generate_expression(using)?;
30505        }
30506        if !e.columns.is_empty() {
30507            self.write("(");
30508            for (i, col) in e.columns.iter().enumerate() {
30509                if i > 0 {
30510                    self.write(", ");
30511                }
30512                self.generate_expression(col)?;
30513            }
30514            self.write(")");
30515        }
30516        if let Some(partition_by) = &e.partition_by {
30517            self.write_space();
30518            self.write_keyword("PARTITION BY");
30519            self.write_space();
30520            self.generate_expression(partition_by)?;
30521        }
30522        if let Some(where_) = &e.where_ {
30523            self.write_space();
30524            self.generate_expression(where_)?;
30525        }
30526        if let Some(include) = &e.include {
30527            self.write_space();
30528            self.write_keyword("INCLUDE");
30529            self.write(" (");
30530            self.generate_expression(include)?;
30531            self.write(")");
30532        }
30533        if let Some(with_storage) = &e.with_storage {
30534            self.write_space();
30535            self.write_keyword("WITH");
30536            self.write(" (");
30537            self.generate_expression(with_storage)?;
30538            self.write(")");
30539        }
30540        if let Some(tablespace) = &e.tablespace {
30541            self.write_space();
30542            self.write_keyword("USING INDEX TABLESPACE");
30543            self.write_space();
30544            self.generate_expression(tablespace)?;
30545        }
30546        Ok(())
30547    }
30548
30549    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
30550        // Python: this INDEX [FOR target] (expressions)
30551        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
30552        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
30553        if let Expression::Identifier(id) = &*e.this {
30554            self.write_keyword(&id.name);
30555        } else {
30556            self.generate_expression(&e.this)?;
30557        }
30558        self.write_space();
30559        self.write_keyword("INDEX");
30560        if let Some(target) = &e.target {
30561            self.write_space();
30562            self.write_keyword("FOR");
30563            self.write_space();
30564            if let Expression::Identifier(id) = &**target {
30565                self.write_keyword(&id.name);
30566            } else {
30567                self.generate_expression(target)?;
30568            }
30569        }
30570        // Always output parentheses (even if empty, e.g. USE INDEX ())
30571        self.write(" (");
30572        for (i, expr) in e.expressions.iter().enumerate() {
30573            if i > 0 {
30574                self.write(", ");
30575            }
30576            self.generate_expression(expr)?;
30577        }
30578        self.write(")");
30579        Ok(())
30580    }
30581
30582    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
30583        // INHERITS (table1, table2, ...)
30584        self.write_keyword("INHERITS");
30585        self.write(" (");
30586        for (i, expr) in e.expressions.iter().enumerate() {
30587            if i > 0 {
30588                self.write(", ");
30589            }
30590            self.generate_expression(expr)?;
30591        }
30592        self.write(")");
30593        Ok(())
30594    }
30595
30596    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
30597        // INPUT(model)
30598        self.write_keyword("INPUT");
30599        self.write("(");
30600        self.generate_expression(&e.this)?;
30601        self.write(")");
30602        Ok(())
30603    }
30604
30605    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
30606        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
30607        if let Some(input_format) = &e.input_format {
30608            self.write_keyword("INPUTFORMAT");
30609            self.write_space();
30610            self.generate_expression(input_format)?;
30611        }
30612        if let Some(output_format) = &e.output_format {
30613            if e.input_format.is_some() {
30614                self.write(" ");
30615            }
30616            self.write_keyword("OUTPUTFORMAT");
30617            self.write_space();
30618            self.generate_expression(output_format)?;
30619        }
30620        Ok(())
30621    }
30622
30623    fn generate_install(&mut self, e: &Install) -> Result<()> {
30624        // [FORCE] INSTALL extension [FROM source]
30625        if e.force.is_some() {
30626            self.write_keyword("FORCE");
30627            self.write_space();
30628        }
30629        self.write_keyword("INSTALL");
30630        self.write_space();
30631        self.generate_expression(&e.this)?;
30632        if let Some(from) = &e.from_ {
30633            self.write_space();
30634            self.write_keyword("FROM");
30635            self.write_space();
30636            self.generate_expression(from)?;
30637        }
30638        Ok(())
30639    }
30640
30641    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
30642        // INTERVAL 'expression' unit
30643        self.write_keyword("INTERVAL");
30644        self.write_space();
30645        // When a unit is specified and the expression is a number,
30646        self.generate_expression(&e.expression)?;
30647        if let Some(unit) = &e.unit {
30648            self.write_space();
30649            self.write(unit);
30650        }
30651        Ok(())
30652    }
30653
30654    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
30655        // unit TO unit (e.g., HOUR TO SECOND)
30656        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
30657        self.write_space();
30658        self.write_keyword("TO");
30659        self.write_space();
30660        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
30661        Ok(())
30662    }
30663
30664    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
30665        // INTO [TEMPORARY|UNLOGGED] table
30666        self.write_keyword("INTO");
30667        if e.temporary {
30668            self.write_keyword(" TEMPORARY");
30669        }
30670        if e.unlogged.is_some() {
30671            self.write_keyword(" UNLOGGED");
30672        }
30673        if let Some(this) = &e.this {
30674            self.write_space();
30675            self.generate_expression(this)?;
30676        }
30677        if !e.expressions.is_empty() {
30678            self.write(" (");
30679            for (i, expr) in e.expressions.iter().enumerate() {
30680                if i > 0 {
30681                    self.write(", ");
30682                }
30683                self.generate_expression(expr)?;
30684            }
30685            self.write(")");
30686        }
30687        Ok(())
30688    }
30689
30690    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
30691        // Python: this expression (e.g., _utf8 'string')
30692        self.generate_expression(&e.this)?;
30693        self.write_space();
30694        self.generate_expression(&e.expression)?;
30695        Ok(())
30696    }
30697
30698    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
30699        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
30700        self.write_keyword("WITH");
30701        if e.no.is_some() {
30702            self.write_keyword(" NO");
30703        }
30704        if e.concurrent.is_some() {
30705            self.write_keyword(" CONCURRENT");
30706        }
30707        self.write_keyword(" ISOLATED LOADING");
30708        if let Some(target) = &e.target {
30709            self.write_space();
30710            self.generate_expression(target)?;
30711        }
30712        Ok(())
30713    }
30714
30715    fn generate_json(&mut self, e: &JSON) -> Result<()> {
30716        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
30717        self.write_keyword("JSON");
30718        if let Some(this) = &e.this {
30719            self.write_space();
30720            self.generate_expression(this)?;
30721        }
30722        if let Some(with_) = &e.with_ {
30723            // Check if it's a truthy boolean
30724            if let Expression::Boolean(b) = with_.as_ref() {
30725                if b.value {
30726                    self.write_keyword(" WITH");
30727                } else {
30728                    self.write_keyword(" WITHOUT");
30729                }
30730            }
30731        }
30732        if e.unique {
30733            self.write_keyword(" UNIQUE KEYS");
30734        }
30735        Ok(())
30736    }
30737
30738    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
30739        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
30740        self.write_keyword("JSON_ARRAY");
30741        self.write("(");
30742        for (i, expr) in e.expressions.iter().enumerate() {
30743            if i > 0 {
30744                self.write(", ");
30745            }
30746            self.generate_expression(expr)?;
30747        }
30748        if let Some(null_handling) = &e.null_handling {
30749            self.write_space();
30750            self.generate_expression(null_handling)?;
30751        }
30752        if let Some(return_type) = &e.return_type {
30753            self.write_space();
30754            self.write_keyword("RETURNING");
30755            self.write_space();
30756            self.generate_expression(return_type)?;
30757        }
30758        if e.strict.is_some() {
30759            self.write_space();
30760            self.write_keyword("STRICT");
30761        }
30762        self.write(")");
30763        Ok(())
30764    }
30765
30766    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
30767        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
30768        self.write_keyword("JSON_ARRAYAGG");
30769        self.write("(");
30770        self.generate_expression(&e.this)?;
30771        if let Some(order) = &e.order {
30772            self.write_space();
30773            // Order is stored as an OrderBy expression
30774            if let Expression::OrderBy(ob) = order.as_ref() {
30775                self.write_keyword("ORDER BY");
30776                self.write_space();
30777                for (i, ord) in ob.expressions.iter().enumerate() {
30778                    if i > 0 {
30779                        self.write(", ");
30780                    }
30781                    self.generate_ordered(ord)?;
30782                }
30783            } else {
30784                // Fallback: generate the expression directly
30785                self.generate_expression(order)?;
30786            }
30787        }
30788        if let Some(null_handling) = &e.null_handling {
30789            self.write_space();
30790            self.generate_expression(null_handling)?;
30791        }
30792        if let Some(return_type) = &e.return_type {
30793            self.write_space();
30794            self.write_keyword("RETURNING");
30795            self.write_space();
30796            self.generate_expression(return_type)?;
30797        }
30798        if e.strict.is_some() {
30799            self.write_space();
30800            self.write_keyword("STRICT");
30801        }
30802        self.write(")");
30803        Ok(())
30804    }
30805
30806    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
30807        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
30808        self.write_keyword("JSON_OBJECTAGG");
30809        self.write("(");
30810        for (i, expr) in e.expressions.iter().enumerate() {
30811            if i > 0 {
30812                self.write(", ");
30813            }
30814            self.generate_expression(expr)?;
30815        }
30816        if let Some(null_handling) = &e.null_handling {
30817            self.write_space();
30818            self.generate_expression(null_handling)?;
30819        }
30820        if let Some(unique_keys) = &e.unique_keys {
30821            self.write_space();
30822            if let Expression::Boolean(b) = unique_keys.as_ref() {
30823                if b.value {
30824                    self.write_keyword("WITH UNIQUE KEYS");
30825                } else {
30826                    self.write_keyword("WITHOUT UNIQUE KEYS");
30827                }
30828            }
30829        }
30830        if let Some(return_type) = &e.return_type {
30831            self.write_space();
30832            self.write_keyword("RETURNING");
30833            self.write_space();
30834            self.generate_expression(return_type)?;
30835        }
30836        self.write(")");
30837        Ok(())
30838    }
30839
30840    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
30841        // JSON_ARRAY_APPEND(this, path, value, ...)
30842        self.write_keyword("JSON_ARRAY_APPEND");
30843        self.write("(");
30844        self.generate_expression(&e.this)?;
30845        for expr in &e.expressions {
30846            self.write(", ");
30847            self.generate_expression(expr)?;
30848        }
30849        self.write(")");
30850        Ok(())
30851    }
30852
30853    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
30854        // JSON_ARRAY_CONTAINS(this, expression)
30855        self.write_keyword("JSON_ARRAY_CONTAINS");
30856        self.write("(");
30857        self.generate_expression(&e.this)?;
30858        self.write(", ");
30859        self.generate_expression(&e.expression)?;
30860        self.write(")");
30861        Ok(())
30862    }
30863
30864    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
30865        // JSON_ARRAY_INSERT(this, path, value, ...)
30866        self.write_keyword("JSON_ARRAY_INSERT");
30867        self.write("(");
30868        self.generate_expression(&e.this)?;
30869        for expr in &e.expressions {
30870            self.write(", ");
30871            self.generate_expression(expr)?;
30872        }
30873        self.write(")");
30874        Ok(())
30875    }
30876
30877    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
30878        // JSONB_EXISTS(this, path)
30879        self.write_keyword("JSONB_EXISTS");
30880        self.write("(");
30881        self.generate_expression(&e.this)?;
30882        if let Some(path) = &e.path {
30883            self.write(", ");
30884            self.generate_expression(path)?;
30885        }
30886        self.write(")");
30887        Ok(())
30888    }
30889
30890    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
30891        // JSONB_EXTRACT_SCALAR(this, expression)
30892        self.write_keyword("JSONB_EXTRACT_SCALAR");
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_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
30902        // JSONB_OBJECT_AGG(this, expression)
30903        self.write_keyword("JSONB_OBJECT_AGG");
30904        self.write("(");
30905        self.generate_expression(&e.this)?;
30906        self.write(", ");
30907        self.generate_expression(&e.expression)?;
30908        self.write(")");
30909        Ok(())
30910    }
30911
30912    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
30913        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
30914        if let Some(nested_schema) = &e.nested_schema {
30915            self.write_keyword("NESTED");
30916            if let Some(path) = &e.path {
30917                self.write_space();
30918                self.write_keyword("PATH");
30919                self.write_space();
30920                self.generate_expression(path)?;
30921            }
30922            self.write_space();
30923            self.generate_expression(nested_schema)?;
30924        } else {
30925            if let Some(this) = &e.this {
30926                self.generate_expression(this)?;
30927            }
30928            if let Some(kind) = &e.kind {
30929                self.write_space();
30930                self.write(kind);
30931            }
30932            if e.format_json {
30933                self.write_space();
30934                self.write_keyword("FORMAT JSON");
30935            }
30936            if let Some(path) = &e.path {
30937                self.write_space();
30938                self.write_keyword("PATH");
30939                self.write_space();
30940                self.generate_expression(path)?;
30941            }
30942            if e.ordinality.is_some() {
30943                self.write_keyword(" FOR ORDINALITY");
30944            }
30945        }
30946        Ok(())
30947    }
30948
30949    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
30950        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
30951        self.write_keyword("JSON_EXISTS");
30952        self.write("(");
30953        self.generate_expression(&e.this)?;
30954        if let Some(path) = &e.path {
30955            self.write(", ");
30956            self.generate_expression(path)?;
30957        }
30958        if let Some(passing) = &e.passing {
30959            self.write_space();
30960            self.write_keyword("PASSING");
30961            self.write_space();
30962            self.generate_expression(passing)?;
30963        }
30964        if let Some(on_condition) = &e.on_condition {
30965            self.write_space();
30966            self.generate_expression(on_condition)?;
30967        }
30968        self.write(")");
30969        Ok(())
30970    }
30971
30972    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
30973        self.generate_expression(&e.this)?;
30974        self.write(".:");
30975        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
30976        // wrap the entire type string in double quotes.
30977        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
30978        if Self::data_type_has_nested_expressions(&e.to) {
30979            // Generate the data type to a temporary string buffer, then wrap in quotes
30980            let saved = std::mem::take(&mut self.output);
30981            self.generate_data_type(&e.to)?;
30982            let type_sql = std::mem::replace(&mut self.output, saved);
30983            self.write("\"");
30984            self.write(&type_sql);
30985            self.write("\"");
30986        } else {
30987            self.generate_data_type(&e.to)?;
30988        }
30989        Ok(())
30990    }
30991
30992    /// Check if a DataType has nested type expressions (sub-types).
30993    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
30994    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
30995        matches!(
30996            dt,
30997            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
30998        )
30999    }
31000
31001    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
31002        // JSON_EXTRACT_ARRAY(this, expression)
31003        self.write_keyword("JSON_EXTRACT_ARRAY");
31004        self.write("(");
31005        self.generate_expression(&e.this)?;
31006        if let Some(expr) = &e.expression {
31007            self.write(", ");
31008            self.generate_expression(expr)?;
31009        }
31010        self.write(")");
31011        Ok(())
31012    }
31013
31014    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
31015        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
31016        if let Some(option) = &e.option {
31017            self.generate_expression(option)?;
31018            self.write_space();
31019        }
31020        self.write_keyword("QUOTES");
31021        if e.scalar.is_some() {
31022            self.write_keyword(" SCALAR_ONLY");
31023        }
31024        Ok(())
31025    }
31026
31027    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
31028        // JSON_EXTRACT_SCALAR(this, expression)
31029        self.write_keyword("JSON_EXTRACT_SCALAR");
31030        self.write("(");
31031        self.generate_expression(&e.this)?;
31032        self.write(", ");
31033        self.generate_expression(&e.expression)?;
31034        self.write(")");
31035        Ok(())
31036    }
31037
31038    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
31039        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
31040        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
31041        // Otherwise output JSON_EXTRACT(this, expression)
31042        if e.variant_extract.is_some() {
31043            use crate::dialects::DialectType;
31044            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
31045                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
31046                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
31047                // bracket notation: c:["x-y"] instead of c:x-y
31048                self.generate_expression(&e.this)?;
31049                self.write(":");
31050                match e.expression.as_ref() {
31051                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
31052                        let Literal::String(s) = lit.as_ref() else {
31053                            unreachable!()
31054                        };
31055                        self.write_databricks_json_path(s);
31056                    }
31057                    _ => {
31058                        // Fallback: generate as-is (shouldn't happen in typical cases)
31059                        self.generate_expression(&e.expression)?;
31060                    }
31061                }
31062            } else {
31063                // Snowflake and others: use GET_PATH(col, 'path')
31064                self.write_keyword("GET_PATH");
31065                self.write("(");
31066                self.generate_expression(&e.this)?;
31067                self.write(", ");
31068                self.generate_expression(&e.expression)?;
31069                self.write(")");
31070            }
31071        } else {
31072            self.write_keyword("JSON_EXTRACT");
31073            self.write("(");
31074            self.generate_expression(&e.this)?;
31075            self.write(", ");
31076            self.generate_expression(&e.expression)?;
31077            for expr in &e.expressions {
31078                self.write(", ");
31079                self.generate_expression(expr)?;
31080            }
31081            self.write(")");
31082        }
31083        Ok(())
31084    }
31085
31086    /// Write a Databricks JSON colon-path, using bracket notation for keys
31087    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
31088    /// Safe identifier regex: ^[_a-zA-Z]\w*$
31089    fn write_databricks_json_path(&mut self, path: &str) {
31090        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
31091        // it was already formatted by the parser - output as-is
31092        if path.starts_with("[\"") || path.starts_with("['") {
31093            self.write(path);
31094            return;
31095        }
31096        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
31097        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
31098        // e.g., "x-y" -> ["x-y"]
31099        let mut first = true;
31100        for segment in path.split('.') {
31101            if !first {
31102                self.write(".");
31103            }
31104            first = false;
31105            // Check if there's a bracket subscript in this segment: "items[0]"
31106            if let Some(bracket_pos) = segment.find('[') {
31107                let key = &segment[..bracket_pos];
31108                let subscript = &segment[bracket_pos..];
31109                if key.is_empty() {
31110                    // Bracket notation at start of segment (e.g., already formatted)
31111                    self.write(segment);
31112                } else if Self::is_safe_json_path_key(key) {
31113                    self.write(key);
31114                    self.write(subscript);
31115                } else {
31116                    self.write("[\"");
31117                    self.write(key);
31118                    self.write("\"]");
31119                    self.write(subscript);
31120                }
31121            } else if Self::is_safe_json_path_key(segment) {
31122                self.write(segment);
31123            } else {
31124                self.write("[\"");
31125                self.write(segment);
31126                self.write("\"]");
31127            }
31128        }
31129    }
31130
31131    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
31132    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
31133    fn is_safe_json_path_key(key: &str) -> bool {
31134        if key.is_empty() {
31135            return false;
31136        }
31137        let mut chars = key.chars();
31138        let first = chars.next().unwrap();
31139        if first != '_' && !first.is_ascii_alphabetic() {
31140            return false;
31141        }
31142        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
31143    }
31144
31145    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
31146        // Output: {expr} FORMAT JSON
31147        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
31148        if let Some(this) = &e.this {
31149            self.generate_expression(this)?;
31150            self.write_space();
31151        }
31152        self.write_keyword("FORMAT JSON");
31153        Ok(())
31154    }
31155
31156    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
31157        // key: value (for JSON objects)
31158        self.generate_expression(&e.this)?;
31159        self.write(": ");
31160        self.generate_expression(&e.expression)?;
31161        Ok(())
31162    }
31163
31164    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
31165        // JSON_KEYS(this, expression, expressions...)
31166        self.write_keyword("JSON_KEYS");
31167        self.write("(");
31168        self.generate_expression(&e.this)?;
31169        if let Some(expr) = &e.expression {
31170            self.write(", ");
31171            self.generate_expression(expr)?;
31172        }
31173        for expr in &e.expressions {
31174            self.write(", ");
31175            self.generate_expression(expr)?;
31176        }
31177        self.write(")");
31178        Ok(())
31179    }
31180
31181    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
31182        // JSON_KEYS(this, expression)
31183        self.write_keyword("JSON_KEYS");
31184        self.write("(");
31185        self.generate_expression(&e.this)?;
31186        if let Some(expr) = &e.expression {
31187            self.write(", ");
31188            self.generate_expression(expr)?;
31189        }
31190        self.write(")");
31191        Ok(())
31192    }
31193
31194    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
31195        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
31196        // The path components are concatenated without spaces
31197        let mut path_str = String::new();
31198        for expr in &e.expressions {
31199            match expr {
31200                Expression::JSONPathRoot(_) => {
31201                    path_str.push('$');
31202                }
31203                Expression::JSONPathKey(k) => {
31204                    // .key or ."key" (quote if key has special characters)
31205                    if let Expression::Literal(lit) = k.this.as_ref() {
31206                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
31207                            path_str.push('.');
31208                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
31209                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
31210                            if needs_quoting {
31211                                path_str.push('"');
31212                                path_str.push_str(s);
31213                                path_str.push('"');
31214                            } else {
31215                                path_str.push_str(s);
31216                            }
31217                        }
31218                    }
31219                }
31220                Expression::JSONPathSubscript(s) => {
31221                    // [index]
31222                    if let Expression::Literal(lit) = s.this.as_ref() {
31223                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
31224                            path_str.push('[');
31225                            path_str.push_str(n);
31226                            path_str.push(']');
31227                        }
31228                    }
31229                }
31230                _ => {
31231                    // For other path parts, try to generate them
31232                    let mut temp_gen = Self::with_arc_config(self.config.clone());
31233                    temp_gen.generate_expression(expr)?;
31234                    path_str.push_str(&temp_gen.output);
31235                }
31236            }
31237        }
31238        // Output as quoted string
31239        self.write("'");
31240        self.write(&path_str);
31241        self.write("'");
31242        Ok(())
31243    }
31244
31245    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
31246        // JSON path filter: ?(predicate)
31247        self.write("?(");
31248        self.generate_expression(&e.this)?;
31249        self.write(")");
31250        Ok(())
31251    }
31252
31253    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
31254        // JSON path key: .key or ["key"]
31255        self.write(".");
31256        self.generate_expression(&e.this)?;
31257        Ok(())
31258    }
31259
31260    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
31261        // JSON path recursive descent: ..
31262        self.write("..");
31263        if let Some(this) = &e.this {
31264            self.generate_expression(this)?;
31265        }
31266        Ok(())
31267    }
31268
31269    fn generate_json_path_root(&mut self) -> Result<()> {
31270        // JSON path root: $
31271        self.write("$");
31272        Ok(())
31273    }
31274
31275    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
31276        // JSON path script: (expression)
31277        self.write("(");
31278        self.generate_expression(&e.this)?;
31279        self.write(")");
31280        Ok(())
31281    }
31282
31283    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
31284        // JSON path selector: *
31285        self.generate_expression(&e.this)?;
31286        Ok(())
31287    }
31288
31289    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
31290        // JSON path slice: [start:end:step]
31291        self.write("[");
31292        if let Some(start) = &e.start {
31293            self.generate_expression(start)?;
31294        }
31295        self.write(":");
31296        if let Some(end) = &e.end {
31297            self.generate_expression(end)?;
31298        }
31299        if let Some(step) = &e.step {
31300            self.write(":");
31301            self.generate_expression(step)?;
31302        }
31303        self.write("]");
31304        Ok(())
31305    }
31306
31307    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
31308        // JSON path subscript: [index] or [*]
31309        self.write("[");
31310        self.generate_expression(&e.this)?;
31311        self.write("]");
31312        Ok(())
31313    }
31314
31315    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
31316        // JSON path union: [key1, key2, ...]
31317        self.write("[");
31318        for (i, expr) in e.expressions.iter().enumerate() {
31319            if i > 0 {
31320                self.write(", ");
31321            }
31322            self.generate_expression(expr)?;
31323        }
31324        self.write("]");
31325        Ok(())
31326    }
31327
31328    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
31329        // JSON_REMOVE(this, path1, path2, ...)
31330        self.write_keyword("JSON_REMOVE");
31331        self.write("(");
31332        self.generate_expression(&e.this)?;
31333        for expr in &e.expressions {
31334            self.write(", ");
31335            self.generate_expression(expr)?;
31336        }
31337        self.write(")");
31338        Ok(())
31339    }
31340
31341    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
31342        // COLUMNS(col1 type, col2 type, ...)
31343        // When pretty printing and content is too wide, format with each column on a separate line
31344        self.write_keyword("COLUMNS");
31345        self.write("(");
31346
31347        if self.config.pretty && !e.expressions.is_empty() {
31348            // First, generate all expressions into strings to check width
31349            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
31350            for expr in &e.expressions {
31351                let mut temp_gen = Generator::with_arc_config(self.config.clone());
31352                temp_gen.generate_expression(expr)?;
31353                expr_strings.push(temp_gen.output);
31354            }
31355
31356            // Check if total width exceeds max_text_width
31357            if self.too_wide(&expr_strings) {
31358                // Pretty print: each column on its own line
31359                self.write_newline();
31360                self.indent_level += 1;
31361                for (i, expr_str) in expr_strings.iter().enumerate() {
31362                    if i > 0 {
31363                        self.write(",");
31364                        self.write_newline();
31365                    }
31366                    self.write_indent();
31367                    self.write(expr_str);
31368                }
31369                self.write_newline();
31370                self.indent_level -= 1;
31371                self.write_indent();
31372            } else {
31373                // Compact: all on one line
31374                for (i, expr_str) in expr_strings.iter().enumerate() {
31375                    if i > 0 {
31376                        self.write(", ");
31377                    }
31378                    self.write(expr_str);
31379                }
31380            }
31381        } else {
31382            // Non-pretty mode: compact format
31383            for (i, expr) in e.expressions.iter().enumerate() {
31384                if i > 0 {
31385                    self.write(", ");
31386                }
31387                self.generate_expression(expr)?;
31388            }
31389        }
31390        self.write(")");
31391        Ok(())
31392    }
31393
31394    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
31395        // JSON_SET(this, path, value, ...)
31396        self.write_keyword("JSON_SET");
31397        self.write("(");
31398        self.generate_expression(&e.this)?;
31399        for expr in &e.expressions {
31400            self.write(", ");
31401            self.generate_expression(expr)?;
31402        }
31403        self.write(")");
31404        Ok(())
31405    }
31406
31407    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
31408        // JSON_STRIP_NULLS(this, expression)
31409        self.write_keyword("JSON_STRIP_NULLS");
31410        self.write("(");
31411        self.generate_expression(&e.this)?;
31412        if let Some(expr) = &e.expression {
31413            self.write(", ");
31414            self.generate_expression(expr)?;
31415        }
31416        self.write(")");
31417        Ok(())
31418    }
31419
31420    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
31421        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
31422        self.write_keyword("JSON_TABLE");
31423        self.write("(");
31424        self.generate_expression(&e.this)?;
31425        if let Some(path) = &e.path {
31426            self.write(", ");
31427            self.generate_expression(path)?;
31428        }
31429        if let Some(error_handling) = &e.error_handling {
31430            self.write_space();
31431            self.generate_expression(error_handling)?;
31432        }
31433        if let Some(empty_handling) = &e.empty_handling {
31434            self.write_space();
31435            self.generate_expression(empty_handling)?;
31436        }
31437        if let Some(schema) = &e.schema {
31438            self.write_space();
31439            self.generate_expression(schema)?;
31440        }
31441        self.write(")");
31442        Ok(())
31443    }
31444
31445    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
31446        // JSON_TYPE(this)
31447        self.write_keyword("JSON_TYPE");
31448        self.write("(");
31449        self.generate_expression(&e.this)?;
31450        self.write(")");
31451        Ok(())
31452    }
31453
31454    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
31455        // JSON_VALUE(this, path RETURNING type ON condition)
31456        self.write_keyword("JSON_VALUE");
31457        self.write("(");
31458        self.generate_expression(&e.this)?;
31459        if let Some(path) = &e.path {
31460            self.write(", ");
31461            self.generate_expression(path)?;
31462        }
31463        if let Some(returning) = &e.returning {
31464            self.write_space();
31465            self.write_keyword("RETURNING");
31466            self.write_space();
31467            self.generate_expression(returning)?;
31468        }
31469        if let Some(on_condition) = &e.on_condition {
31470            self.write_space();
31471            self.generate_expression(on_condition)?;
31472        }
31473        self.write(")");
31474        Ok(())
31475    }
31476
31477    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
31478        // JSON_VALUE_ARRAY(this)
31479        self.write_keyword("JSON_VALUE_ARRAY");
31480        self.write("(");
31481        self.generate_expression(&e.this)?;
31482        self.write(")");
31483        Ok(())
31484    }
31485
31486    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
31487        // JAROWINKLER_SIMILARITY(str1, str2)
31488        self.write_keyword("JAROWINKLER_SIMILARITY");
31489        self.write("(");
31490        self.generate_expression(&e.this)?;
31491        self.write(", ");
31492        self.generate_expression(&e.expression)?;
31493        self.write(")");
31494        Ok(())
31495    }
31496
31497    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
31498        // Python: this(expressions)
31499        self.generate_expression(&e.this)?;
31500        self.write("(");
31501        for (i, expr) in e.expressions.iter().enumerate() {
31502            if i > 0 {
31503                self.write(", ");
31504            }
31505            self.generate_expression(expr)?;
31506        }
31507        self.write(")");
31508        Ok(())
31509    }
31510
31511    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
31512        // Python: {no}{local}{dual}{before}{after}JOURNAL
31513        if e.no.is_some() {
31514            self.write_keyword("NO ");
31515        }
31516        if let Some(local) = &e.local {
31517            self.generate_expression(local)?;
31518            self.write_space();
31519        }
31520        if e.dual.is_some() {
31521            self.write_keyword("DUAL ");
31522        }
31523        if e.before.is_some() {
31524            self.write_keyword("BEFORE ");
31525        }
31526        if e.after.is_some() {
31527            self.write_keyword("AFTER ");
31528        }
31529        self.write_keyword("JOURNAL");
31530        Ok(())
31531    }
31532
31533    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
31534        // LANGUAGE language_name
31535        self.write_keyword("LANGUAGE");
31536        self.write_space();
31537        self.generate_expression(&e.this)?;
31538        Ok(())
31539    }
31540
31541    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
31542        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
31543        if e.view.is_some() {
31544            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
31545            self.write_keyword("LATERAL VIEW");
31546            if e.outer.is_some() {
31547                self.write_space();
31548                self.write_keyword("OUTER");
31549            }
31550            self.write_space();
31551            self.generate_expression(&e.this)?;
31552            if let Some(alias) = &e.alias {
31553                self.write_space();
31554                self.write(alias);
31555            }
31556        } else {
31557            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
31558            self.write_keyword("LATERAL");
31559            self.write_space();
31560            self.generate_expression(&e.this)?;
31561            if e.ordinality.is_some() {
31562                self.write_space();
31563                self.write_keyword("WITH ORDINALITY");
31564            }
31565            if let Some(alias) = &e.alias {
31566                self.write_space();
31567                self.write_keyword("AS");
31568                self.write_space();
31569                self.write(alias);
31570                if !e.column_aliases.is_empty() {
31571                    self.write("(");
31572                    for (i, col) in e.column_aliases.iter().enumerate() {
31573                        if i > 0 {
31574                            self.write(", ");
31575                        }
31576                        self.write(col);
31577                    }
31578                    self.write(")");
31579                }
31580            }
31581        }
31582        Ok(())
31583    }
31584
31585    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
31586        // Python: LIKE this [options]
31587        self.write_keyword("LIKE");
31588        self.write_space();
31589        self.generate_expression(&e.this)?;
31590        for expr in &e.expressions {
31591            self.write_space();
31592            self.generate_expression(expr)?;
31593        }
31594        Ok(())
31595    }
31596
31597    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
31598        self.write_keyword("LIMIT");
31599        self.write_space();
31600        self.write_limit_expr(&e.this)?;
31601        if e.percent {
31602            self.write_space();
31603            self.write_keyword("PERCENT");
31604        }
31605        // Emit any comments that were captured from before the LIMIT keyword
31606        for comment in &e.comments {
31607            self.write(" ");
31608            self.write_formatted_comment(comment);
31609        }
31610        Ok(())
31611    }
31612
31613    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
31614        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
31615        if e.percent.is_some() {
31616            self.write_keyword(" PERCENT");
31617        }
31618        if e.rows.is_some() {
31619            self.write_keyword(" ROWS");
31620        }
31621        if e.with_ties.is_some() {
31622            self.write_keyword(" WITH TIES");
31623        } else if e.rows.is_some() {
31624            self.write_keyword(" ONLY");
31625        }
31626        Ok(())
31627    }
31628
31629    fn generate_list(&mut self, e: &List) -> Result<()> {
31630        use crate::dialects::DialectType;
31631        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
31632
31633        // Check if this is a subquery-based list (LIST(SELECT ...))
31634        if e.expressions.len() == 1 {
31635            if let Expression::Select(_) = &e.expressions[0] {
31636                self.write_keyword("LIST");
31637                self.write("(");
31638                self.generate_expression(&e.expressions[0])?;
31639                self.write(")");
31640                return Ok(());
31641            }
31642        }
31643
31644        // For Materialize, output as LIST[expr, expr, ...]
31645        if is_materialize {
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        } else {
31656            // For other dialects, output as LIST(expr, expr, ...)
31657            self.write_keyword("LIST");
31658            self.write("(");
31659            for (i, expr) in e.expressions.iter().enumerate() {
31660                if i > 0 {
31661                    self.write(", ");
31662                }
31663                self.generate_expression(expr)?;
31664            }
31665            self.write(")");
31666        }
31667        Ok(())
31668    }
31669
31670    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
31671        // Check if this is a subquery-based map (MAP(SELECT ...))
31672        if let Expression::Select(_) = &*e.this {
31673            self.write_keyword("MAP");
31674            self.write("(");
31675            self.generate_expression(&e.this)?;
31676            self.write(")");
31677            return Ok(());
31678        }
31679
31680        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
31681
31682        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
31683        self.write_keyword("MAP");
31684        if is_duckdb {
31685            self.write(" {");
31686        } else {
31687            self.write("[");
31688        }
31689        if let Expression::Struct(s) = &*e.this {
31690            for (i, (_, expr)) in s.fields.iter().enumerate() {
31691                if i > 0 {
31692                    self.write(", ");
31693                }
31694                if let Expression::PropertyEQ(op) = expr {
31695                    self.generate_expression(&op.left)?;
31696                    if is_duckdb {
31697                        self.write(": ");
31698                    } else {
31699                        self.write(" => ");
31700                    }
31701                    self.generate_expression(&op.right)?;
31702                } else {
31703                    self.generate_expression(expr)?;
31704                }
31705            }
31706        }
31707        if is_duckdb {
31708            self.write("}");
31709        } else {
31710            self.write("]");
31711        }
31712        Ok(())
31713    }
31714
31715    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
31716        // Python: LOCALTIME or LOCALTIME(precision)
31717        self.write_keyword("LOCALTIME");
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_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
31727        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
31728        self.write_keyword("LOCALTIMESTAMP");
31729        if let Some(precision) = &e.this {
31730            self.write("(");
31731            self.generate_expression(precision)?;
31732            self.write(")");
31733        }
31734        Ok(())
31735    }
31736
31737    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
31738        // LOCATION 'path'
31739        self.write_keyword("LOCATION");
31740        self.write_space();
31741        self.generate_expression(&e.this)?;
31742        Ok(())
31743    }
31744
31745    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
31746        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
31747        if e.update.is_some() {
31748            if e.key.is_some() {
31749                self.write_keyword("FOR NO KEY UPDATE");
31750            } else {
31751                self.write_keyword("FOR UPDATE");
31752            }
31753        } else {
31754            if e.key.is_some() {
31755                self.write_keyword("FOR KEY SHARE");
31756            } else {
31757                self.write_keyword("FOR SHARE");
31758            }
31759        }
31760        if !e.expressions.is_empty() {
31761            self.write_keyword(" OF ");
31762            for (i, expr) in e.expressions.iter().enumerate() {
31763                if i > 0 {
31764                    self.write(", ");
31765                }
31766                self.generate_expression(expr)?;
31767            }
31768        }
31769        // Handle wait option following Python sqlglot convention:
31770        // - Boolean(true) -> NOWAIT
31771        // - Boolean(false) -> SKIP LOCKED
31772        // - Literal (number) -> WAIT n
31773        if let Some(wait) = &e.wait {
31774            match wait.as_ref() {
31775                Expression::Boolean(b) => {
31776                    if b.value {
31777                        self.write_keyword(" NOWAIT");
31778                    } else {
31779                        self.write_keyword(" SKIP LOCKED");
31780                    }
31781                }
31782                _ => {
31783                    // It's a literal (number), output WAIT n
31784                    self.write_keyword(" WAIT ");
31785                    self.generate_expression(wait)?;
31786                }
31787            }
31788        }
31789        Ok(())
31790    }
31791
31792    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
31793        // LOCK property
31794        self.write_keyword("LOCK");
31795        self.write_space();
31796        self.generate_expression(&e.this)?;
31797        Ok(())
31798    }
31799
31800    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
31801        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
31802        self.write_keyword("LOCKING");
31803        self.write_space();
31804        self.write(&e.kind);
31805        if let Some(this) = &e.this {
31806            self.write_space();
31807            self.generate_expression(this)?;
31808        }
31809        if let Some(for_or_in) = &e.for_or_in {
31810            self.write_space();
31811            self.generate_expression(for_or_in)?;
31812        }
31813        if let Some(lock_type) = &e.lock_type {
31814            self.write_space();
31815            self.generate_expression(lock_type)?;
31816        }
31817        if e.override_.is_some() {
31818            self.write_keyword(" OVERRIDE");
31819        }
31820        Ok(())
31821    }
31822
31823    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
31824        // this expression
31825        self.generate_expression(&e.this)?;
31826        self.write_space();
31827        self.generate_expression(&e.expression)?;
31828        Ok(())
31829    }
31830
31831    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
31832        // [NO] LOG
31833        if e.no.is_some() {
31834            self.write_keyword("NO ");
31835        }
31836        self.write_keyword("LOG");
31837        Ok(())
31838    }
31839
31840    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
31841        // MD5(this, expressions...)
31842        self.write_keyword("MD5");
31843        self.write("(");
31844        self.generate_expression(&e.this)?;
31845        for expr in &e.expressions {
31846            self.write(", ");
31847            self.generate_expression(expr)?;
31848        }
31849        self.write(")");
31850        Ok(())
31851    }
31852
31853    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
31854        // ML.FORECAST(model, [params])
31855        self.write_keyword("ML.FORECAST");
31856        self.write("(");
31857        self.generate_expression(&e.this)?;
31858        if let Some(expression) = &e.expression {
31859            self.write(", ");
31860            self.generate_expression(expression)?;
31861        }
31862        if let Some(params) = &e.params_struct {
31863            self.write(", ");
31864            self.generate_expression(params)?;
31865        }
31866        self.write(")");
31867        Ok(())
31868    }
31869
31870    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
31871        // ML.TRANSLATE(model, input, [params])
31872        self.write_keyword("ML.TRANSLATE");
31873        self.write("(");
31874        self.generate_expression(&e.this)?;
31875        self.write(", ");
31876        self.generate_expression(&e.expression)?;
31877        if let Some(params) = &e.params_struct {
31878            self.write(", ");
31879            self.generate_expression(params)?;
31880        }
31881        self.write(")");
31882        Ok(())
31883    }
31884
31885    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
31886        // MAKE_INTERVAL(years => x, months => y, ...)
31887        self.write_keyword("MAKE_INTERVAL");
31888        self.write("(");
31889        let mut first = true;
31890        if let Some(year) = &e.year {
31891            self.write("years => ");
31892            self.generate_expression(year)?;
31893            first = false;
31894        }
31895        if let Some(month) = &e.month {
31896            if !first {
31897                self.write(", ");
31898            }
31899            self.write("months => ");
31900            self.generate_expression(month)?;
31901            first = false;
31902        }
31903        if let Some(week) = &e.week {
31904            if !first {
31905                self.write(", ");
31906            }
31907            self.write("weeks => ");
31908            self.generate_expression(week)?;
31909            first = false;
31910        }
31911        if let Some(day) = &e.day {
31912            if !first {
31913                self.write(", ");
31914            }
31915            self.write("days => ");
31916            self.generate_expression(day)?;
31917            first = false;
31918        }
31919        if let Some(hour) = &e.hour {
31920            if !first {
31921                self.write(", ");
31922            }
31923            self.write("hours => ");
31924            self.generate_expression(hour)?;
31925            first = false;
31926        }
31927        if let Some(minute) = &e.minute {
31928            if !first {
31929                self.write(", ");
31930            }
31931            self.write("mins => ");
31932            self.generate_expression(minute)?;
31933            first = false;
31934        }
31935        if let Some(second) = &e.second {
31936            if !first {
31937                self.write(", ");
31938            }
31939            self.write("secs => ");
31940            self.generate_expression(second)?;
31941        }
31942        self.write(")");
31943        Ok(())
31944    }
31945
31946    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
31947        // MANHATTAN_DISTANCE(vector1, vector2)
31948        self.write_keyword("MANHATTAN_DISTANCE");
31949        self.write("(");
31950        self.generate_expression(&e.this)?;
31951        self.write(", ");
31952        self.generate_expression(&e.expression)?;
31953        self.write(")");
31954        Ok(())
31955    }
31956
31957    fn generate_map(&mut self, e: &Map) -> Result<()> {
31958        // MAP(key1, value1, key2, value2, ...)
31959        self.write_keyword("MAP");
31960        self.write("(");
31961        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
31962            if i > 0 {
31963                self.write(", ");
31964            }
31965            self.generate_expression(key)?;
31966            self.write(", ");
31967            self.generate_expression(value)?;
31968        }
31969        self.write(")");
31970        Ok(())
31971    }
31972
31973    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
31974        // MAP_CAT(map1, map2)
31975        self.write_keyword("MAP_CAT");
31976        self.write("(");
31977        self.generate_expression(&e.this)?;
31978        self.write(", ");
31979        self.generate_expression(&e.expression)?;
31980        self.write(")");
31981        Ok(())
31982    }
31983
31984    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
31985        // MAP_DELETE(map, key1, key2, ...)
31986        self.write_keyword("MAP_DELETE");
31987        self.write("(");
31988        self.generate_expression(&e.this)?;
31989        for expr in &e.expressions {
31990            self.write(", ");
31991            self.generate_expression(expr)?;
31992        }
31993        self.write(")");
31994        Ok(())
31995    }
31996
31997    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
31998        // MAP_INSERT(map, key, value, [update_flag])
31999        self.write_keyword("MAP_INSERT");
32000        self.write("(");
32001        self.generate_expression(&e.this)?;
32002        if let Some(key) = &e.key {
32003            self.write(", ");
32004            self.generate_expression(key)?;
32005        }
32006        if let Some(value) = &e.value {
32007            self.write(", ");
32008            self.generate_expression(value)?;
32009        }
32010        if let Some(update_flag) = &e.update_flag {
32011            self.write(", ");
32012            self.generate_expression(update_flag)?;
32013        }
32014        self.write(")");
32015        Ok(())
32016    }
32017
32018    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
32019        // MAP_PICK(map, key1, key2, ...)
32020        self.write_keyword("MAP_PICK");
32021        self.write("(");
32022        self.generate_expression(&e.this)?;
32023        for expr in &e.expressions {
32024            self.write(", ");
32025            self.generate_expression(expr)?;
32026        }
32027        self.write(")");
32028        Ok(())
32029    }
32030
32031    fn generate_masking_policy_column_constraint(
32032        &mut self,
32033        e: &MaskingPolicyColumnConstraint,
32034    ) -> Result<()> {
32035        // Python: MASKING POLICY name [USING (cols)]
32036        self.write_keyword("MASKING POLICY");
32037        self.write_space();
32038        self.generate_expression(&e.this)?;
32039        if !e.expressions.is_empty() {
32040            self.write_keyword(" USING");
32041            self.write(" (");
32042            for (i, expr) in e.expressions.iter().enumerate() {
32043                if i > 0 {
32044                    self.write(", ");
32045                }
32046                self.generate_expression(expr)?;
32047            }
32048            self.write(")");
32049        }
32050        Ok(())
32051    }
32052
32053    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
32054        if matches!(
32055            self.config.dialect,
32056            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
32057        ) {
32058            if e.expressions.len() > 1 {
32059                self.write("(");
32060            }
32061            for (i, expr) in e.expressions.iter().enumerate() {
32062                if i > 0 {
32063                    self.write_keyword(" OR ");
32064                }
32065                self.generate_expression(expr)?;
32066                self.write_space();
32067                self.write("@@");
32068                self.write_space();
32069                self.generate_expression(&e.this)?;
32070            }
32071            if e.expressions.len() > 1 {
32072                self.write(")");
32073            }
32074            return Ok(());
32075        }
32076
32077        // MATCH(columns) AGAINST(expr [modifier])
32078        self.write_keyword("MATCH");
32079        self.write("(");
32080        for (i, expr) in e.expressions.iter().enumerate() {
32081            if i > 0 {
32082                self.write(", ");
32083            }
32084            self.generate_expression(expr)?;
32085        }
32086        self.write(")");
32087        self.write_keyword(" AGAINST");
32088        self.write("(");
32089        self.generate_expression(&e.this)?;
32090        if let Some(modifier) = &e.modifier {
32091            self.write_space();
32092            self.generate_expression(modifier)?;
32093        }
32094        self.write(")");
32095        Ok(())
32096    }
32097
32098    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
32099        // Python: [window_frame] this
32100        if let Some(window_frame) = &e.window_frame {
32101            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
32102            self.write_space();
32103        }
32104        self.generate_expression(&e.this)?;
32105        Ok(())
32106    }
32107
32108    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
32109        // MATERIALIZED [this]
32110        self.write_keyword("MATERIALIZED");
32111        if let Some(this) = &e.this {
32112            self.write_space();
32113            self.generate_expression(this)?;
32114        }
32115        Ok(())
32116    }
32117
32118    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
32119        // MERGE INTO target USING source ON condition WHEN ...
32120        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
32121        if let Some(with_) = &e.with_ {
32122            if let Expression::With(with_clause) = with_.as_ref() {
32123                self.generate_with(with_clause)?;
32124                self.write_space();
32125            } else {
32126                self.generate_expression(with_)?;
32127                self.write_space();
32128            }
32129        }
32130        self.write_keyword("MERGE INTO");
32131        self.write_space();
32132        if matches!(self.config.dialect, Some(crate::DialectType::Oracle)) {
32133            if let Expression::Alias(alias) = e.this.as_ref() {
32134                self.generate_expression(&alias.this)?;
32135                self.write_space();
32136                self.generate_identifier(&alias.alias)?;
32137            } else {
32138                self.generate_expression(&e.this)?;
32139            }
32140        } else {
32141            self.generate_expression(&e.this)?;
32142        }
32143
32144        // USING clause - newline before in pretty mode
32145        if self.config.pretty {
32146            self.write_newline();
32147            self.write_indent();
32148        } else {
32149            self.write_space();
32150        }
32151        self.write_keyword("USING");
32152        self.write_space();
32153        self.generate_expression(&e.using)?;
32154
32155        // ON clause - newline before in pretty mode
32156        if let Some(on) = &e.on {
32157            if self.config.pretty {
32158                self.write_newline();
32159                self.write_indent();
32160            } else {
32161                self.write_space();
32162            }
32163            self.write_keyword("ON");
32164            self.write_space();
32165            self.generate_expression(on)?;
32166        }
32167        // DuckDB USING (key_columns) clause
32168        if let Some(using_cond) = &e.using_cond {
32169            self.write_space();
32170            self.write_keyword("USING");
32171            self.write_space();
32172            self.write("(");
32173            // using_cond is a Tuple containing the column identifiers
32174            if let Expression::Tuple(tuple) = using_cond.as_ref() {
32175                for (i, col) in tuple.expressions.iter().enumerate() {
32176                    if i > 0 {
32177                        self.write(", ");
32178                    }
32179                    self.generate_expression(col)?;
32180                }
32181            } else {
32182                self.generate_expression(using_cond)?;
32183            }
32184            self.write(")");
32185        }
32186        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
32187        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
32188        if matches!(
32189            self.config.dialect,
32190            Some(crate::DialectType::PostgreSQL)
32191                | Some(crate::DialectType::Redshift)
32192                | Some(crate::DialectType::Trino)
32193                | Some(crate::DialectType::Presto)
32194                | Some(crate::DialectType::Athena)
32195        ) {
32196            let mut names = Vec::new();
32197            match e.this.as_ref() {
32198                Expression::Alias(a) => {
32199                    // e.g., "x AS z" -> strip both "x" and "z"
32200                    if let Expression::Table(t) = &a.this {
32201                        names.push(t.name.name.clone());
32202                    } else if let Expression::Identifier(id) = &a.this {
32203                        names.push(id.name.clone());
32204                    }
32205                    names.push(a.alias.name.clone());
32206                }
32207                Expression::Table(t) => {
32208                    names.push(t.name.name.clone());
32209                }
32210                Expression::Identifier(id) => {
32211                    names.push(id.name.clone());
32212                }
32213                _ => {}
32214            }
32215            self.merge_strip_qualifiers = names;
32216        }
32217
32218        // WHEN clauses - newline before each in pretty mode
32219        if let Some(whens) = &e.whens {
32220            if self.config.pretty {
32221                self.write_newline();
32222                self.write_indent();
32223            } else {
32224                self.write_space();
32225            }
32226            self.generate_expression(whens)?;
32227        }
32228
32229        // Restore merge_strip_qualifiers
32230        self.merge_strip_qualifiers = saved_merge_strip;
32231
32232        // OUTPUT/RETURNING clause - newline before in pretty mode
32233        if let Some(returning) = &e.returning {
32234            if self.config.pretty {
32235                self.write_newline();
32236                self.write_indent();
32237            } else {
32238                self.write_space();
32239            }
32240            self.generate_expression(returning)?;
32241        }
32242        Ok(())
32243    }
32244
32245    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
32246        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
32247        if e.no.is_some() {
32248            self.write_keyword("NO MERGEBLOCKRATIO");
32249        } else if e.default.is_some() {
32250            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
32251        } else {
32252            self.write_keyword("MERGEBLOCKRATIO");
32253            self.write("=");
32254            if let Some(this) = &e.this {
32255                self.generate_expression(this)?;
32256            }
32257            if e.percent.is_some() {
32258                self.write_keyword(" PERCENT");
32259            }
32260        }
32261        Ok(())
32262    }
32263
32264    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
32265        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
32266        self.write_keyword("TTL");
32267        let pretty_clickhouse = self.config.pretty
32268            && matches!(
32269                self.config.dialect,
32270                Some(crate::dialects::DialectType::ClickHouse)
32271            );
32272
32273        if pretty_clickhouse {
32274            self.write_newline();
32275            self.indent_level += 1;
32276            for (i, expr) in e.expressions.iter().enumerate() {
32277                if i > 0 {
32278                    self.write(",");
32279                    self.write_newline();
32280                }
32281                self.write_indent();
32282                self.generate_expression(expr)?;
32283            }
32284            self.indent_level -= 1;
32285        } else {
32286            self.write_space();
32287            for (i, expr) in e.expressions.iter().enumerate() {
32288                if i > 0 {
32289                    self.write(", ");
32290                }
32291                self.generate_expression(expr)?;
32292            }
32293        }
32294
32295        if let Some(where_) = &e.where_ {
32296            if pretty_clickhouse {
32297                self.write_newline();
32298                if let Expression::Where(w) = where_.as_ref() {
32299                    self.write_indent();
32300                    self.write_keyword("WHERE");
32301                    self.write_newline();
32302                    self.indent_level += 1;
32303                    self.write_indent();
32304                    self.generate_expression(&w.this)?;
32305                    self.indent_level -= 1;
32306                } else {
32307                    self.write_indent();
32308                    self.generate_expression(where_)?;
32309                }
32310            } else {
32311                self.write_space();
32312                self.generate_expression(where_)?;
32313            }
32314        }
32315        if let Some(group) = &e.group {
32316            if pretty_clickhouse {
32317                self.write_newline();
32318                if let Expression::Group(g) = group.as_ref() {
32319                    self.write_indent();
32320                    self.write_keyword("GROUP BY");
32321                    self.write_newline();
32322                    self.indent_level += 1;
32323                    for (i, expr) in g.expressions.iter().enumerate() {
32324                        if i > 0 {
32325                            self.write(",");
32326                            self.write_newline();
32327                        }
32328                        self.write_indent();
32329                        self.generate_expression(expr)?;
32330                    }
32331                    self.indent_level -= 1;
32332                } else {
32333                    self.write_indent();
32334                    self.generate_expression(group)?;
32335                }
32336            } else {
32337                self.write_space();
32338                self.generate_expression(group)?;
32339            }
32340        }
32341        if let Some(aggregates) = &e.aggregates {
32342            if pretty_clickhouse {
32343                self.write_newline();
32344                self.write_indent();
32345                self.write_keyword("SET");
32346                self.write_newline();
32347                self.indent_level += 1;
32348                if let Expression::Tuple(t) = aggregates.as_ref() {
32349                    for (i, agg) in t.expressions.iter().enumerate() {
32350                        if i > 0 {
32351                            self.write(",");
32352                            self.write_newline();
32353                        }
32354                        self.write_indent();
32355                        self.generate_expression(agg)?;
32356                    }
32357                } else {
32358                    self.write_indent();
32359                    self.generate_expression(aggregates)?;
32360                }
32361                self.indent_level -= 1;
32362            } else {
32363                self.write_space();
32364                self.write_keyword("SET");
32365                self.write_space();
32366                if let Expression::Tuple(t) = aggregates.as_ref() {
32367                    for (i, agg) in t.expressions.iter().enumerate() {
32368                        if i > 0 {
32369                            self.write(", ");
32370                        }
32371                        self.generate_expression(agg)?;
32372                    }
32373                } else {
32374                    self.generate_expression(aggregates)?;
32375                }
32376            }
32377        }
32378        Ok(())
32379    }
32380
32381    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
32382        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
32383        self.generate_expression(&e.this)?;
32384        if e.delete.is_some() {
32385            self.write_keyword(" DELETE");
32386        }
32387        if let Some(recompress) = &e.recompress {
32388            self.write_keyword(" RECOMPRESS ");
32389            self.generate_expression(recompress)?;
32390        }
32391        if let Some(to_disk) = &e.to_disk {
32392            self.write_keyword(" TO DISK ");
32393            self.generate_expression(to_disk)?;
32394        }
32395        if let Some(to_volume) = &e.to_volume {
32396            self.write_keyword(" TO VOLUME ");
32397            self.generate_expression(to_volume)?;
32398        }
32399        Ok(())
32400    }
32401
32402    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
32403        // MINHASH(this, expressions...)
32404        self.write_keyword("MINHASH");
32405        self.write("(");
32406        self.generate_expression(&e.this)?;
32407        for expr in &e.expressions {
32408            self.write(", ");
32409            self.generate_expression(expr)?;
32410        }
32411        self.write(")");
32412        Ok(())
32413    }
32414
32415    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
32416        // model!attribute - Snowflake syntax
32417        self.generate_expression(&e.this)?;
32418        self.write("!");
32419        self.generate_expression(&e.expression)?;
32420        Ok(())
32421    }
32422
32423    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
32424        // MONTHNAME(this)
32425        self.write_keyword("MONTHNAME");
32426        self.write("(");
32427        self.generate_expression(&e.this)?;
32428        self.write(")");
32429        Ok(())
32430    }
32431
32432    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
32433        // Output leading comments
32434        for comment in &e.leading_comments {
32435            self.write_formatted_comment(comment);
32436            if self.config.pretty {
32437                self.write_newline();
32438                self.write_indent();
32439            } else {
32440                self.write_space();
32441            }
32442        }
32443        // Python: INSERT [OVERWRITE] kind expressions source
32444        self.write_keyword("INSERT");
32445        if e.overwrite {
32446            self.write_space();
32447            self.write_keyword("OVERWRITE");
32448        }
32449        self.write_space();
32450        self.write(&e.kind);
32451        if self.config.pretty {
32452            self.indent_level += 1;
32453            for expr in &e.expressions {
32454                self.write_newline();
32455                self.write_indent();
32456                self.generate_expression(expr)?;
32457            }
32458            self.indent_level -= 1;
32459        } else {
32460            for expr in &e.expressions {
32461                self.write_space();
32462                self.generate_expression(expr)?;
32463            }
32464        }
32465        if let Some(source) = &e.source {
32466            if self.config.pretty {
32467                self.write_newline();
32468                self.write_indent();
32469            } else {
32470                self.write_space();
32471            }
32472            self.generate_expression(source)?;
32473        }
32474        Ok(())
32475    }
32476
32477    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
32478        // Python: NEXT VALUE FOR this [OVER (order)]
32479        self.write_keyword("NEXT VALUE FOR");
32480        self.write_space();
32481        self.generate_expression(&e.this)?;
32482        if let Some(order) = &e.order {
32483            self.write_space();
32484            self.write_keyword("OVER");
32485            self.write(" (");
32486            self.generate_expression(order)?;
32487            self.write(")");
32488        }
32489        Ok(())
32490    }
32491
32492    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
32493        // NORMAL(mean, stddev, gen)
32494        self.write_keyword("NORMAL");
32495        self.write("(");
32496        self.generate_expression(&e.this)?;
32497        if let Some(stddev) = &e.stddev {
32498            self.write(", ");
32499            self.generate_expression(stddev)?;
32500        }
32501        if let Some(gen) = &e.gen {
32502            self.write(", ");
32503            self.generate_expression(gen)?;
32504        }
32505        self.write(")");
32506        Ok(())
32507    }
32508
32509    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
32510        // NORMALIZE(this, form) or CASEFOLD version
32511        if e.is_casefold.is_some() {
32512            self.write_keyword("NORMALIZE_AND_CASEFOLD");
32513        } else {
32514            self.write_keyword("NORMALIZE");
32515        }
32516        self.write("(");
32517        self.generate_expression(&e.this)?;
32518        if let Some(form) = &e.form {
32519            self.write(", ");
32520            self.generate_expression(form)?;
32521        }
32522        self.write(")");
32523        Ok(())
32524    }
32525
32526    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
32527        // Python: [NOT ]NULL
32528        if e.allow_null.is_none() {
32529            self.write_keyword("NOT ");
32530        }
32531        self.write_keyword("NULL");
32532        Ok(())
32533    }
32534
32535    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
32536        // NULLIF(this, expression)
32537        self.write_keyword("NULLIF");
32538        self.write("(");
32539        self.generate_expression(&e.this)?;
32540        self.write(", ");
32541        self.generate_expression(&e.expression)?;
32542        self.write(")");
32543        Ok(())
32544    }
32545
32546    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
32547        // FORMAT(this, format, culture)
32548        self.write_keyword("FORMAT");
32549        self.write("(");
32550        self.generate_expression(&e.this)?;
32551        self.write(", '");
32552        self.write(&e.format);
32553        self.write("'");
32554        if let Some(culture) = &e.culture {
32555            self.write(", ");
32556            self.generate_expression(culture)?;
32557        }
32558        self.write(")");
32559        Ok(())
32560    }
32561
32562    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
32563        // OBJECT_AGG(key, value)
32564        self.write_keyword("OBJECT_AGG");
32565        self.write("(");
32566        self.generate_expression(&e.this)?;
32567        self.write(", ");
32568        self.generate_expression(&e.expression)?;
32569        self.write(")");
32570        Ok(())
32571    }
32572
32573    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
32574        // Python: Just returns the name
32575        self.generate_expression(&e.this)?;
32576        Ok(())
32577    }
32578
32579    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
32580        // OBJECT_INSERT(obj, key, value, [update_flag])
32581        self.write_keyword("OBJECT_INSERT");
32582        self.write("(");
32583        self.generate_expression(&e.this)?;
32584        if let Some(key) = &e.key {
32585            self.write(", ");
32586            self.generate_expression(key)?;
32587        }
32588        if let Some(value) = &e.value {
32589            self.write(", ");
32590            self.generate_expression(value)?;
32591        }
32592        if let Some(update_flag) = &e.update_flag {
32593            self.write(", ");
32594            self.generate_expression(update_flag)?;
32595        }
32596        self.write(")");
32597        Ok(())
32598    }
32599
32600    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
32601        // OFFSET value [ROW|ROWS]
32602        self.write_keyword("OFFSET");
32603        self.write_space();
32604        self.generate_expression(&e.this)?;
32605        // Output ROWS keyword only for TSQL/Oracle targets
32606        if e.rows == Some(true)
32607            && matches!(
32608                self.config.dialect,
32609                Some(crate::dialects::DialectType::TSQL)
32610                    | Some(crate::dialects::DialectType::Oracle)
32611            )
32612        {
32613            self.write_space();
32614            self.write_keyword("ROWS");
32615        }
32616        Ok(())
32617    }
32618
32619    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
32620        // QUALIFY condition (Snowflake/BigQuery)
32621        self.write_keyword("QUALIFY");
32622        self.write_space();
32623        self.generate_expression(&e.this)?;
32624        Ok(())
32625    }
32626
32627    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
32628        // ON CLUSTER cluster_name
32629        self.write_keyword("ON CLUSTER");
32630        self.write_space();
32631        self.generate_expression(&e.this)?;
32632        Ok(())
32633    }
32634
32635    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
32636        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
32637        self.write_keyword("ON COMMIT");
32638        if e.delete.is_some() {
32639            self.write_keyword(" DELETE ROWS");
32640        } else {
32641            self.write_keyword(" PRESERVE ROWS");
32642        }
32643        Ok(())
32644    }
32645
32646    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
32647        // Python: error/empty/null handling
32648        if let Some(empty) = &e.empty {
32649            self.generate_expression(empty)?;
32650            self.write_keyword(" ON EMPTY");
32651        }
32652        if let Some(error) = &e.error {
32653            if e.empty.is_some() {
32654                self.write_space();
32655            }
32656            self.generate_expression(error)?;
32657            self.write_keyword(" ON ERROR");
32658        }
32659        if let Some(null) = &e.null {
32660            if e.empty.is_some() || e.error.is_some() {
32661                self.write_space();
32662            }
32663            self.generate_expression(null)?;
32664            self.write_keyword(" ON NULL");
32665        }
32666        Ok(())
32667    }
32668
32669    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
32670        // Materialize doesn't support ON CONFLICT - skip entirely
32671        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
32672            return Ok(());
32673        }
32674        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
32675        if e.duplicate.is_some() {
32676            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
32677            self.write_keyword("ON DUPLICATE KEY UPDATE");
32678            for (i, expr) in e.expressions.iter().enumerate() {
32679                if i > 0 {
32680                    self.write(",");
32681                }
32682                self.write_space();
32683                self.generate_expression(expr)?;
32684            }
32685            return Ok(());
32686        } else {
32687            self.write_keyword("ON CONFLICT");
32688        }
32689        if let Some(constraint) = &e.constraint {
32690            self.write_keyword(" ON CONSTRAINT ");
32691            self.generate_expression(constraint)?;
32692        }
32693        if let Some(conflict_keys) = &e.conflict_keys {
32694            // conflict_keys can be a Tuple containing expressions
32695            if let Expression::Tuple(t) = conflict_keys.as_ref() {
32696                self.write("(");
32697                for (i, expr) in t.expressions.iter().enumerate() {
32698                    if i > 0 {
32699                        self.write(", ");
32700                    }
32701                    self.generate_expression(expr)?;
32702                }
32703                self.write(")");
32704            } else {
32705                self.write("(");
32706                self.generate_expression(conflict_keys)?;
32707                self.write(")");
32708            }
32709        }
32710        if let Some(index_predicate) = &e.index_predicate {
32711            self.write_keyword(" WHERE ");
32712            self.generate_expression(index_predicate)?;
32713        }
32714        if let Some(action) = &e.action {
32715            // Check if action is "NOTHING" or an UPDATE set
32716            if let Expression::Identifier(id) = action.as_ref() {
32717                if id.name.eq_ignore_ascii_case("NOTHING") {
32718                    self.write_keyword(" DO NOTHING");
32719                } else {
32720                    self.write_keyword(" DO ");
32721                    self.generate_expression(action)?;
32722                }
32723            } else if let Expression::Tuple(t) = action.as_ref() {
32724                // DO UPDATE SET col1 = val1, col2 = val2
32725                self.write_keyword(" DO UPDATE SET ");
32726                for (i, expr) in t.expressions.iter().enumerate() {
32727                    if i > 0 {
32728                        self.write(", ");
32729                    }
32730                    self.generate_expression(expr)?;
32731                }
32732            } else {
32733                self.write_keyword(" DO ");
32734                self.generate_expression(action)?;
32735            }
32736        }
32737        // WHERE clause for the UPDATE action
32738        if let Some(where_) = &e.where_ {
32739            self.write_keyword(" WHERE ");
32740            self.generate_expression(where_)?;
32741        }
32742        Ok(())
32743    }
32744
32745    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
32746        // ON property_value
32747        self.write_keyword("ON");
32748        self.write_space();
32749        self.generate_expression(&e.this)?;
32750        Ok(())
32751    }
32752
32753    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
32754        // Python: this expression (e.g., column opclass)
32755        self.generate_expression(&e.this)?;
32756        self.write_space();
32757        self.generate_expression(&e.expression)?;
32758        Ok(())
32759    }
32760
32761    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
32762        // Python: OPENJSON(this[, path]) [WITH (columns)]
32763        self.write_keyword("OPENJSON");
32764        self.write("(");
32765        self.generate_expression(&e.this)?;
32766        if let Some(path) = &e.path {
32767            self.write(", ");
32768            self.generate_expression(path)?;
32769        }
32770        self.write(")");
32771        if !e.expressions.is_empty() {
32772            self.write_keyword(" WITH");
32773            if self.config.pretty {
32774                self.write(" (\n");
32775                self.indent_level += 2;
32776                for (i, expr) in e.expressions.iter().enumerate() {
32777                    if i > 0 {
32778                        self.write(",\n");
32779                    }
32780                    self.write_indent();
32781                    self.generate_expression(expr)?;
32782                }
32783                self.write("\n");
32784                self.indent_level -= 2;
32785                self.write(")");
32786            } else {
32787                self.write(" (");
32788                for (i, expr) in e.expressions.iter().enumerate() {
32789                    if i > 0 {
32790                        self.write(", ");
32791                    }
32792                    self.generate_expression(expr)?;
32793                }
32794                self.write(")");
32795            }
32796        }
32797        Ok(())
32798    }
32799
32800    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
32801        // Python: this kind [path] [AS JSON]
32802        self.generate_expression(&e.this)?;
32803        self.write_space();
32804        // Use parsed data_type if available, otherwise fall back to kind string
32805        if let Some(ref dt) = e.data_type {
32806            self.generate_data_type(dt)?;
32807        } else if !e.kind.is_empty() {
32808            self.write(&e.kind);
32809        }
32810        if let Some(path) = &e.path {
32811            self.write_space();
32812            self.generate_expression(path)?;
32813        }
32814        if e.as_json.is_some() {
32815            self.write_keyword(" AS JSON");
32816        }
32817        Ok(())
32818    }
32819
32820    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
32821        // this OPERATOR(op) expression
32822        self.generate_expression(&e.this)?;
32823        self.write_space();
32824        if let Some(op) = &e.operator {
32825            self.write_keyword("OPERATOR");
32826            self.write("(");
32827            self.generate_expression(op)?;
32828            self.write(")");
32829        }
32830        // Emit inline comments between OPERATOR() and the RHS
32831        for comment in &e.comments {
32832            self.write_space();
32833            self.write_formatted_comment(comment);
32834        }
32835        self.write_space();
32836        self.generate_expression(&e.expression)?;
32837        Ok(())
32838    }
32839
32840    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
32841        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
32842        self.write_keyword("ORDER BY");
32843        let pretty_clickhouse_single_paren = self.config.pretty
32844            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
32845            && e.expressions.len() == 1
32846            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
32847        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
32848            && e.expressions.len() == 1
32849            && matches!(e.expressions[0].this, Expression::Tuple(_))
32850            && !e.expressions[0].desc
32851            && e.expressions[0].nulls_first.is_none();
32852
32853        if pretty_clickhouse_single_paren {
32854            self.write_space();
32855            if let Expression::Paren(p) = &e.expressions[0].this {
32856                self.write("(");
32857                self.write_newline();
32858                self.indent_level += 1;
32859                self.write_indent();
32860                self.generate_expression(&p.this)?;
32861                self.indent_level -= 1;
32862                self.write_newline();
32863                self.write(")");
32864            }
32865            return Ok(());
32866        }
32867
32868        if clickhouse_single_tuple {
32869            self.write_space();
32870            if let Expression::Tuple(t) = &e.expressions[0].this {
32871                self.write("(");
32872                for (i, expr) in t.expressions.iter().enumerate() {
32873                    if i > 0 {
32874                        self.write(", ");
32875                    }
32876                    self.generate_expression(expr)?;
32877                }
32878                self.write(")");
32879            }
32880            return Ok(());
32881        }
32882
32883        self.write_space();
32884        for (i, ordered) in e.expressions.iter().enumerate() {
32885            if i > 0 {
32886                self.write(", ");
32887            }
32888            self.generate_expression(&ordered.this)?;
32889            if ordered.desc {
32890                self.write_space();
32891                self.write_keyword("DESC");
32892            } else if ordered.explicit_asc {
32893                self.write_space();
32894                self.write_keyword("ASC");
32895            }
32896            if let Some(nulls_first) = ordered.nulls_first {
32897                // In Dremio, NULLS LAST is the default, so skip generating it
32898                let skip_nulls_last =
32899                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
32900                if !skip_nulls_last {
32901                    self.write_space();
32902                    self.write_keyword("NULLS");
32903                    self.write_space();
32904                    if nulls_first {
32905                        self.write_keyword("FIRST");
32906                    } else {
32907                        self.write_keyword("LAST");
32908                    }
32909                }
32910            }
32911        }
32912        Ok(())
32913    }
32914
32915    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
32916        // OUTPUT(model)
32917        self.write_keyword("OUTPUT");
32918        self.write("(");
32919        if self.config.pretty {
32920            self.indent_level += 1;
32921            self.write_newline();
32922            self.write_indent();
32923            self.generate_expression(&e.this)?;
32924            self.indent_level -= 1;
32925            self.write_newline();
32926        } else {
32927            self.generate_expression(&e.this)?;
32928        }
32929        self.write(")");
32930        Ok(())
32931    }
32932
32933    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
32934        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
32935        self.write_keyword("TRUNCATE");
32936        if let Some(this) = &e.this {
32937            self.write_space();
32938            self.generate_expression(this)?;
32939        }
32940        if e.with_count.is_some() {
32941            self.write_keyword(" WITH COUNT");
32942        } else {
32943            self.write_keyword(" WITHOUT COUNT");
32944        }
32945        Ok(())
32946    }
32947
32948    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
32949        // Python: name(expressions)(params)
32950        self.generate_expression(&e.this)?;
32951        self.write("(");
32952        for (i, expr) in e.expressions.iter().enumerate() {
32953            if i > 0 {
32954                self.write(", ");
32955            }
32956            self.generate_expression(expr)?;
32957        }
32958        self.write(")(");
32959        for (i, param) in e.params.iter().enumerate() {
32960            if i > 0 {
32961                self.write(", ");
32962            }
32963            self.generate_expression(param)?;
32964        }
32965        self.write(")");
32966        Ok(())
32967    }
32968
32969    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
32970        // PARSE_DATETIME(format, this) or similar
32971        self.write_keyword("PARSE_DATETIME");
32972        self.write("(");
32973        if let Some(format) = &e.format {
32974            self.write("'");
32975            self.write(format);
32976            self.write("', ");
32977        }
32978        self.generate_expression(&e.this)?;
32979        if let Some(zone) = &e.zone {
32980            self.write(", ");
32981            self.generate_expression(zone)?;
32982        }
32983        self.write(")");
32984        Ok(())
32985    }
32986
32987    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
32988        // PARSE_IP(this, type, permissive)
32989        self.write_keyword("PARSE_IP");
32990        self.write("(");
32991        self.generate_expression(&e.this)?;
32992        if let Some(type_) = &e.type_ {
32993            self.write(", ");
32994            self.generate_expression(type_)?;
32995        }
32996        if let Some(permissive) = &e.permissive {
32997            self.write(", ");
32998            self.generate_expression(permissive)?;
32999        }
33000        self.write(")");
33001        Ok(())
33002    }
33003
33004    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
33005        // PARSE_JSON(this, [expression])
33006        self.write_keyword("PARSE_JSON");
33007        self.write("(");
33008        self.generate_expression(&e.this)?;
33009        if let Some(expression) = &e.expression {
33010            self.write(", ");
33011            self.generate_expression(expression)?;
33012        }
33013        self.write(")");
33014        Ok(())
33015    }
33016
33017    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
33018        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
33019        self.write_keyword("PARSE_TIME");
33020        self.write("(");
33021        self.write(&format!("'{}'", e.format));
33022        self.write(", ");
33023        self.generate_expression(&e.this)?;
33024        self.write(")");
33025        Ok(())
33026    }
33027
33028    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
33029        // PARSE_URL(this, [part_to_extract], [key], [permissive])
33030        self.write_keyword("PARSE_URL");
33031        self.write("(");
33032        self.generate_expression(&e.this)?;
33033        if let Some(part) = &e.part_to_extract {
33034            self.write(", ");
33035            self.generate_expression(part)?;
33036        }
33037        if let Some(key) = &e.key {
33038            self.write(", ");
33039            self.generate_expression(key)?;
33040        }
33041        if let Some(permissive) = &e.permissive {
33042            self.write(", ");
33043            self.generate_expression(permissive)?;
33044        }
33045        self.write(")");
33046        Ok(())
33047    }
33048
33049    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
33050        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
33051        if e.subpartition {
33052            self.write_keyword("SUBPARTITION");
33053        } else {
33054            self.write_keyword("PARTITION");
33055        }
33056        self.write("(");
33057        for (i, expr) in e.expressions.iter().enumerate() {
33058            if i > 0 {
33059                self.write(", ");
33060            }
33061            self.generate_expression(expr)?;
33062        }
33063        self.write(")");
33064        Ok(())
33065    }
33066
33067    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
33068        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
33069        if let Some(this) = &e.this {
33070            if let Some(expression) = &e.expression {
33071                // WITH (MODULUS this, REMAINDER expression)
33072                self.write_keyword("WITH");
33073                self.write(" (");
33074                self.write_keyword("MODULUS");
33075                self.write_space();
33076                self.generate_expression(this)?;
33077                self.write(", ");
33078                self.write_keyword("REMAINDER");
33079                self.write_space();
33080                self.generate_expression(expression)?;
33081                self.write(")");
33082            } else {
33083                // IN (this) - this could be a list
33084                self.write_keyword("IN");
33085                self.write(" (");
33086                self.generate_partition_bound_values(this)?;
33087                self.write(")");
33088            }
33089        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
33090            // FROM (from_expressions) TO (to_expressions)
33091            self.write_keyword("FROM");
33092            self.write(" (");
33093            self.generate_partition_bound_values(from)?;
33094            self.write(") ");
33095            self.write_keyword("TO");
33096            self.write(" (");
33097            self.generate_partition_bound_values(to)?;
33098            self.write(")");
33099        }
33100        Ok(())
33101    }
33102
33103    /// Generate partition bound values - handles Tuple expressions by outputting
33104    /// contents without wrapping parens (since caller provides the parens)
33105    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
33106        if let Expression::Tuple(t) = expr {
33107            for (i, e) in t.expressions.iter().enumerate() {
33108                if i > 0 {
33109                    self.write(", ");
33110                }
33111                self.generate_expression(e)?;
33112            }
33113            Ok(())
33114        } else {
33115            self.generate_expression(expr)
33116        }
33117    }
33118
33119    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
33120        // PARTITION BY LIST (partition_expressions) (create_expressions)
33121        self.write_keyword("PARTITION BY LIST");
33122        if let Some(partition_exprs) = &e.partition_expressions {
33123            self.write(" (");
33124            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
33125            self.generate_doris_partition_expressions(partition_exprs)?;
33126            self.write(")");
33127        }
33128        if let Some(create_exprs) = &e.create_expressions {
33129            self.write(" (");
33130            // Unwrap Tuple for partition definitions
33131            self.generate_doris_partition_definitions(create_exprs)?;
33132            self.write(")");
33133        }
33134        Ok(())
33135    }
33136
33137    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
33138        // PARTITION BY RANGE (partition_expressions) (create_expressions)
33139        self.write_keyword("PARTITION BY RANGE");
33140        if let Some(partition_exprs) = &e.partition_expressions {
33141            self.write(" (");
33142            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
33143            self.generate_doris_partition_expressions(partition_exprs)?;
33144            self.write(")");
33145        }
33146        if let Some(create_exprs) = &e.create_expressions {
33147            self.write(" (");
33148            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
33149            self.generate_doris_partition_definitions(create_exprs)?;
33150            self.write(")");
33151        }
33152        Ok(())
33153    }
33154
33155    /// Generate Doris partition column expressions (unwrap Tuple)
33156    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
33157        if let Expression::Tuple(t) = expr {
33158            for (i, e) in t.expressions.iter().enumerate() {
33159                if i > 0 {
33160                    self.write(", ");
33161                }
33162                self.generate_expression(e)?;
33163            }
33164        } else {
33165            self.generate_expression(expr)?;
33166        }
33167        Ok(())
33168    }
33169
33170    /// Generate Doris partition definitions (comma-separated Partition expressions)
33171    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
33172        match expr {
33173            Expression::Tuple(t) => {
33174                // Multiple partitions, comma-separated
33175                for (i, part) in t.expressions.iter().enumerate() {
33176                    if i > 0 {
33177                        self.write(", ");
33178                    }
33179                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
33180                    if let Expression::Partition(p) = part {
33181                        for (j, inner) in p.expressions.iter().enumerate() {
33182                            if j > 0 {
33183                                self.write(", ");
33184                            }
33185                            self.generate_expression(inner)?;
33186                        }
33187                    } else {
33188                        self.generate_expression(part)?;
33189                    }
33190                }
33191            }
33192            Expression::PartitionByRangePropertyDynamic(_) => {
33193                // Dynamic partition - FROM/TO/INTERVAL
33194                self.generate_expression(expr)?;
33195            }
33196            _ => {
33197                self.generate_expression(expr)?;
33198            }
33199        }
33200        Ok(())
33201    }
33202
33203    fn generate_partition_by_range_property_dynamic(
33204        &mut self,
33205        e: &PartitionByRangePropertyDynamic,
33206    ) -> Result<()> {
33207        if e.use_start_end {
33208            // StarRocks: START ('val') END ('val') EVERY (expr)
33209            if let Some(start) = &e.start {
33210                self.write_keyword("START");
33211                self.write(" (");
33212                self.generate_expression(start)?;
33213                self.write(")");
33214            }
33215            if let Some(end) = &e.end {
33216                self.write_space();
33217                self.write_keyword("END");
33218                self.write(" (");
33219                self.generate_expression(end)?;
33220                self.write(")");
33221            }
33222            if let Some(every) = &e.every {
33223                self.write_space();
33224                self.write_keyword("EVERY");
33225                self.write(" (");
33226                // Use unquoted interval format for StarRocks
33227                self.generate_doris_interval(every)?;
33228                self.write(")");
33229            }
33230        } else {
33231            // Doris: FROM (start) TO (end) INTERVAL n UNIT
33232            if let Some(start) = &e.start {
33233                self.write_keyword("FROM");
33234                self.write(" (");
33235                self.generate_expression(start)?;
33236                self.write(")");
33237            }
33238            if let Some(end) = &e.end {
33239                self.write_space();
33240                self.write_keyword("TO");
33241                self.write(" (");
33242                self.generate_expression(end)?;
33243                self.write(")");
33244            }
33245            if let Some(every) = &e.every {
33246                self.write_space();
33247                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
33248                self.generate_doris_interval(every)?;
33249            }
33250        }
33251        Ok(())
33252    }
33253
33254    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
33255    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
33256        if let Expression::Interval(interval) = expr {
33257            self.write_keyword("INTERVAL");
33258            if let Some(ref value) = interval.this {
33259                self.write_space();
33260                // If the value is a string literal that looks like a number,
33261                // output it without quotes (matching Python sqlglot's
33262                // partitionbyrangepropertydynamic_sql which converts back to number)
33263                match value {
33264                    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()) => {
33265                        if let Literal::String(s) = lit.as_ref() {
33266                            self.write(s);
33267                        }
33268                    }
33269                    _ => {
33270                        self.generate_expression(value)?;
33271                    }
33272                }
33273            }
33274            if let Some(ref unit_spec) = interval.unit {
33275                self.write_space();
33276                self.write_interval_unit_spec(unit_spec)?;
33277            }
33278            Ok(())
33279        } else {
33280            self.generate_expression(expr)
33281        }
33282    }
33283
33284    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
33285        // TRUNCATE(expression, this)
33286        self.write_keyword("TRUNCATE");
33287        self.write("(");
33288        self.generate_expression(&e.expression)?;
33289        self.write(", ");
33290        self.generate_expression(&e.this)?;
33291        self.write(")");
33292        Ok(())
33293    }
33294
33295    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
33296        // Doris: PARTITION name VALUES IN (val1, val2)
33297        self.write_keyword("PARTITION");
33298        self.write_space();
33299        self.generate_expression(&e.this)?;
33300        self.write_space();
33301        self.write_keyword("VALUES IN");
33302        self.write(" (");
33303        for (i, expr) in e.expressions.iter().enumerate() {
33304            if i > 0 {
33305                self.write(", ");
33306            }
33307            self.generate_expression(expr)?;
33308        }
33309        self.write(")");
33310        Ok(())
33311    }
33312
33313    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
33314        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
33315        // TSQL ranges have no expressions and just use `this TO expression`
33316        if e.expressions.is_empty() && e.expression.is_some() {
33317            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
33318            self.generate_expression(&e.this)?;
33319            self.write_space();
33320            self.write_keyword("TO");
33321            self.write_space();
33322            self.generate_expression(e.expression.as_ref().unwrap())?;
33323            return Ok(());
33324        }
33325
33326        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
33327        self.write_keyword("PARTITION");
33328        self.write_space();
33329        self.generate_expression(&e.this)?;
33330        self.write_space();
33331
33332        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
33333        if e.expressions.len() == 1 {
33334            // Single value: VALUES LESS THAN (val)
33335            self.write_keyword("VALUES LESS THAN");
33336            self.write(" (");
33337            self.generate_expression(&e.expressions[0])?;
33338            self.write(")");
33339        } else if !e.expressions.is_empty() {
33340            // Multiple values with Tuple: VALUES [(val1), (val2))
33341            self.write_keyword("VALUES");
33342            self.write(" [");
33343            for (i, expr) in e.expressions.iter().enumerate() {
33344                if i > 0 {
33345                    self.write(", ");
33346                }
33347                // If the expr is a Tuple, generate its contents wrapped in parens
33348                if let Expression::Tuple(t) = expr {
33349                    self.write("(");
33350                    for (j, inner) in t.expressions.iter().enumerate() {
33351                        if j > 0 {
33352                            self.write(", ");
33353                        }
33354                        self.generate_expression(inner)?;
33355                    }
33356                    self.write(")");
33357                } else {
33358                    self.write("(");
33359                    self.generate_expression(expr)?;
33360                    self.write(")");
33361                }
33362            }
33363            self.write(")");
33364        }
33365        Ok(())
33366    }
33367
33368    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
33369        // BUCKET(this, expression)
33370        self.write_keyword("BUCKET");
33371        self.write("(");
33372        self.generate_expression(&e.this)?;
33373        self.write(", ");
33374        self.generate_expression(&e.expression)?;
33375        self.write(")");
33376        Ok(())
33377    }
33378
33379    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
33380        // BigQuery table property: PARTITION BY expression [, expression ...]
33381        self.write_keyword("PARTITION BY");
33382        self.write_space();
33383        for (i, expr) in e.expressions.iter().enumerate() {
33384            if i > 0 {
33385                self.write(", ");
33386            }
33387            self.generate_expression(expr)?;
33388        }
33389        Ok(())
33390    }
33391
33392    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
33393        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
33394        if matches!(
33395            self.config.dialect,
33396            Some(crate::dialects::DialectType::Teradata)
33397                | Some(crate::dialects::DialectType::ClickHouse)
33398        ) {
33399            self.write_keyword("PARTITION BY");
33400        } else {
33401            self.write_keyword("PARTITIONED BY");
33402        }
33403        self.write_space();
33404        // In pretty mode, always use multiline tuple format for PARTITIONED BY
33405        if self.config.pretty {
33406            if let Expression::Tuple(ref tuple) = *e.this {
33407                self.write("(");
33408                self.write_newline();
33409                self.indent_level += 1;
33410                for (i, expr) in tuple.expressions.iter().enumerate() {
33411                    if i > 0 {
33412                        self.write(",");
33413                        self.write_newline();
33414                    }
33415                    self.write_indent();
33416                    self.generate_expression(expr)?;
33417                }
33418                self.indent_level -= 1;
33419                self.write_newline();
33420                self.write(")");
33421            } else {
33422                self.generate_expression(&e.this)?;
33423            }
33424        } else {
33425            self.generate_expression(&e.this)?;
33426        }
33427        Ok(())
33428    }
33429
33430    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
33431        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
33432        self.write_keyword("PARTITION OF");
33433        self.write_space();
33434        self.generate_expression(&e.this)?;
33435        // Check if expression is a PartitionBoundSpec
33436        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
33437            self.write_space();
33438            self.write_keyword("FOR VALUES");
33439            self.write_space();
33440            self.generate_expression(&e.expression)?;
33441        } else {
33442            self.write_space();
33443            self.write_keyword("DEFAULT");
33444        }
33445        Ok(())
33446    }
33447
33448    fn generate_period_for_system_time_constraint(
33449        &mut self,
33450        e: &PeriodForSystemTimeConstraint,
33451    ) -> Result<()> {
33452        // PERIOD FOR SYSTEM_TIME (this, expression)
33453        self.write_keyword("PERIOD FOR SYSTEM_TIME");
33454        self.write(" (");
33455        self.generate_expression(&e.this)?;
33456        self.write(", ");
33457        self.generate_expression(&e.expression)?;
33458        self.write(")");
33459        Ok(())
33460    }
33461
33462    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
33463        // value AS alias
33464        // The alias can be an identifier or an expression (e.g., string concatenation)
33465        self.generate_expression(&e.this)?;
33466        self.write_space();
33467        self.write_keyword("AS");
33468        self.write_space();
33469        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
33470        if self.config.unpivot_aliases_are_identifiers {
33471            match &e.alias {
33472                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
33473                    let Literal::String(s) = lit.as_ref() else {
33474                        unreachable!()
33475                    };
33476                    // Convert string literal to identifier
33477                    self.generate_identifier(&Identifier::new(s.clone()))?;
33478                }
33479                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
33480                    let Literal::Number(n) = lit.as_ref() else {
33481                        unreachable!()
33482                    };
33483                    // Convert number literal to quoted identifier
33484                    let mut id = Identifier::new(n.clone());
33485                    id.quoted = true;
33486                    self.generate_identifier(&id)?;
33487                }
33488                other => {
33489                    self.generate_expression(other)?;
33490                }
33491            }
33492        } else {
33493            self.generate_expression(&e.alias)?;
33494        }
33495        Ok(())
33496    }
33497
33498    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
33499        // ANY or ANY [expression]
33500        self.write_keyword("ANY");
33501        if let Some(this) = &e.this {
33502            self.write_space();
33503            self.generate_expression(this)?;
33504        }
33505        Ok(())
33506    }
33507
33508    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
33509        // ML.PREDICT(MODEL this, expression, [params_struct])
33510        self.write_keyword("ML.PREDICT");
33511        self.write("(");
33512        self.write_keyword("MODEL");
33513        self.write_space();
33514        self.generate_expression(&e.this)?;
33515        self.write(", ");
33516        self.generate_expression(&e.expression)?;
33517        if let Some(params) = &e.params_struct {
33518            self.write(", ");
33519            self.generate_expression(params)?;
33520        }
33521        self.write(")");
33522        Ok(())
33523    }
33524
33525    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
33526        // PREVIOUS_DAY(this, expression)
33527        self.write_keyword("PREVIOUS_DAY");
33528        self.write("(");
33529        self.generate_expression(&e.this)?;
33530        self.write(", ");
33531        self.generate_expression(&e.expression)?;
33532        self.write(")");
33533        Ok(())
33534    }
33535
33536    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
33537        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
33538        self.write_keyword("PRIMARY KEY");
33539        if let Some(name) = &e.this {
33540            self.write_space();
33541            self.generate_expression(name)?;
33542        }
33543        if !e.expressions.is_empty() {
33544            self.write(" (");
33545            for (i, expr) in e.expressions.iter().enumerate() {
33546                if i > 0 {
33547                    self.write(", ");
33548                }
33549                self.generate_expression(expr)?;
33550            }
33551            self.write(")");
33552        }
33553        if let Some(include) = &e.include {
33554            self.write_space();
33555            self.generate_expression(include)?;
33556        }
33557        if !e.options.is_empty() {
33558            self.write_space();
33559            for (i, opt) in e.options.iter().enumerate() {
33560                if i > 0 {
33561                    self.write_space();
33562                }
33563                self.generate_expression(opt)?;
33564            }
33565        }
33566        Ok(())
33567    }
33568
33569    fn generate_primary_key_column_constraint(
33570        &mut self,
33571        _e: &PrimaryKeyColumnConstraint,
33572    ) -> Result<()> {
33573        // PRIMARY KEY constraint at column level
33574        self.write_keyword("PRIMARY KEY");
33575        Ok(())
33576    }
33577
33578    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
33579        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
33580        self.write_keyword("PATH");
33581        self.write_space();
33582        self.generate_expression(&e.this)?;
33583        Ok(())
33584    }
33585
33586    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
33587        // PROJECTION this (expression)
33588        self.write_keyword("PROJECTION");
33589        self.write_space();
33590        self.generate_expression(&e.this)?;
33591        self.write(" (");
33592        self.generate_expression(&e.expression)?;
33593        self.write(")");
33594        Ok(())
33595    }
33596
33597    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
33598        // Properties list
33599        for (i, prop) in e.expressions.iter().enumerate() {
33600            if i > 0 {
33601                self.write(", ");
33602            }
33603            self.generate_expression(prop)?;
33604        }
33605        Ok(())
33606    }
33607
33608    fn generate_property(&mut self, e: &Property) -> Result<()> {
33609        // name=value
33610        self.generate_expression(&e.this)?;
33611        if let Some(value) = &e.value {
33612            self.write("=");
33613            self.generate_expression(value)?;
33614        }
33615        Ok(())
33616    }
33617
33618    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
33619        self.write_keyword("OPTIONS");
33620        if e.entries.is_empty() {
33621            self.write(" ()");
33622            return Ok(());
33623        }
33624
33625        if self.config.pretty {
33626            self.write(" (");
33627            self.write_newline();
33628            self.indent_level += 1;
33629            for (i, entry) in e.entries.iter().enumerate() {
33630                if i > 0 {
33631                    self.write(",");
33632                    self.write_newline();
33633                }
33634                self.write_indent();
33635                self.generate_identifier(&entry.key)?;
33636                self.write("=");
33637                self.generate_expression(&entry.value)?;
33638            }
33639            self.indent_level -= 1;
33640            self.write_newline();
33641            self.write(")");
33642        } else {
33643            self.write(" (");
33644            for (i, entry) in e.entries.iter().enumerate() {
33645                if i > 0 {
33646                    self.write(", ");
33647                }
33648                self.generate_identifier(&entry.key)?;
33649                self.write("=");
33650                self.generate_expression(&entry.value)?;
33651            }
33652            self.write(")");
33653        }
33654        Ok(())
33655    }
33656
33657    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
33658    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
33659        self.write_keyword("OPTIONS");
33660        self.write(" (");
33661        for (i, opt) in options.iter().enumerate() {
33662            if i > 0 {
33663                self.write(", ");
33664            }
33665            self.generate_option_expression(opt)?;
33666        }
33667        self.write(")");
33668        Ok(())
33669    }
33670
33671    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
33672    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
33673        self.write_keyword("PROPERTIES");
33674        self.write(" (");
33675        for (i, prop) in properties.iter().enumerate() {
33676            if i > 0 {
33677                self.write(", ");
33678            }
33679            self.generate_option_expression(prop)?;
33680        }
33681        self.write(")");
33682        Ok(())
33683    }
33684
33685    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
33686    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
33687        self.write_keyword("ENVIRONMENT");
33688        self.write(" (");
33689        for (i, env_item) in environment.iter().enumerate() {
33690            if i > 0 {
33691                self.write(", ");
33692            }
33693            self.generate_environment_expression(env_item)?;
33694        }
33695        self.write(")");
33696        Ok(())
33697    }
33698
33699    /// Generate an environment expression with spaces around =
33700    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
33701        match expr {
33702            Expression::Eq(eq) => {
33703                // Generate key = value with spaces (Databricks ENVIRONMENT style)
33704                self.generate_expression(&eq.left)?;
33705                self.write(" = ");
33706                self.generate_expression(&eq.right)?;
33707                Ok(())
33708            }
33709            _ => self.generate_expression(expr),
33710        }
33711    }
33712
33713    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
33714    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
33715        self.write_keyword("TBLPROPERTIES");
33716        if self.config.pretty {
33717            self.write(" (");
33718            self.write_newline();
33719            self.indent_level += 1;
33720            for (i, opt) in options.iter().enumerate() {
33721                if i > 0 {
33722                    self.write(",");
33723                    self.write_newline();
33724                }
33725                self.write_indent();
33726                self.generate_option_expression(opt)?;
33727            }
33728            self.indent_level -= 1;
33729            self.write_newline();
33730            self.write(")");
33731        } else {
33732            self.write(" (");
33733            for (i, opt) in options.iter().enumerate() {
33734                if i > 0 {
33735                    self.write(", ");
33736                }
33737                self.generate_option_expression(opt)?;
33738            }
33739            self.write(")");
33740        }
33741        Ok(())
33742    }
33743
33744    /// Generate an option expression without spaces around =
33745    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
33746        match expr {
33747            Expression::Eq(eq) => {
33748                // Generate key=value without spaces
33749                self.generate_expression(&eq.left)?;
33750                self.write("=");
33751                self.generate_expression(&eq.right)?;
33752                Ok(())
33753            }
33754            _ => self.generate_expression(expr),
33755        }
33756    }
33757
33758    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
33759        // Just output the name
33760        self.generate_expression(&e.this)?;
33761        Ok(())
33762    }
33763
33764    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
33765        // PUT source_file @stage [options]
33766        self.write_keyword("PUT");
33767        self.write_space();
33768
33769        // Source file path - preserve original quoting
33770        if e.source_quoted {
33771            self.write("'");
33772            self.write(&e.source);
33773            self.write("'");
33774        } else {
33775            self.write(&e.source);
33776        }
33777
33778        self.write_space();
33779
33780        // Target stage reference - output the string directly (includes @)
33781        if let Expression::Literal(lit) = &e.target {
33782            if let Literal::String(s) = lit.as_ref() {
33783                self.write(s);
33784            }
33785        } else {
33786            self.generate_expression(&e.target)?;
33787        }
33788
33789        // Optional parameters: KEY=VALUE
33790        for param in &e.params {
33791            self.write_space();
33792            self.write(&param.name);
33793            if let Some(ref value) = param.value {
33794                self.write("=");
33795                self.generate_expression(value)?;
33796            }
33797        }
33798
33799        Ok(())
33800    }
33801
33802    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
33803        // QUANTILE(this, quantile)
33804        self.write_keyword("QUANTILE");
33805        self.write("(");
33806        self.generate_expression(&e.this)?;
33807        if let Some(quantile) = &e.quantile {
33808            self.write(", ");
33809            self.generate_expression(quantile)?;
33810        }
33811        self.write(")");
33812        Ok(())
33813    }
33814
33815    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
33816        // QUERY_BAND = this [UPDATE] [FOR scope]
33817        if matches!(
33818            self.config.dialect,
33819            Some(crate::dialects::DialectType::Teradata)
33820        ) {
33821            self.write_keyword("SET");
33822            self.write_space();
33823        }
33824        self.write_keyword("QUERY_BAND");
33825        self.write(" = ");
33826        self.generate_expression(&e.this)?;
33827        if e.update.is_some() {
33828            self.write_space();
33829            self.write_keyword("UPDATE");
33830        }
33831        if let Some(scope) = &e.scope {
33832            self.write_space();
33833            self.write_keyword("FOR");
33834            self.write_space();
33835            self.generate_expression(scope)?;
33836        }
33837        Ok(())
33838    }
33839
33840    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
33841        // this = expression
33842        self.generate_expression(&e.this)?;
33843        if let Some(expression) = &e.expression {
33844            self.write(" = ");
33845            self.generate_expression(expression)?;
33846        }
33847        Ok(())
33848    }
33849
33850    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
33851        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
33852        self.write_keyword("TRANSFORM");
33853        self.write("(");
33854        for (i, expr) in e.expressions.iter().enumerate() {
33855            if i > 0 {
33856                self.write(", ");
33857            }
33858            self.generate_expression(expr)?;
33859        }
33860        self.write(")");
33861        if let Some(row_format_before) = &e.row_format_before {
33862            self.write_space();
33863            self.generate_expression(row_format_before)?;
33864        }
33865        if let Some(record_writer) = &e.record_writer {
33866            self.write_space();
33867            self.write_keyword("RECORDWRITER");
33868            self.write_space();
33869            self.generate_expression(record_writer)?;
33870        }
33871        if let Some(command_script) = &e.command_script {
33872            self.write_space();
33873            self.write_keyword("USING");
33874            self.write_space();
33875            self.generate_expression(command_script)?;
33876        }
33877        if let Some(schema) = &e.schema {
33878            self.write_space();
33879            self.write_keyword("AS");
33880            self.write_space();
33881            self.generate_expression(schema)?;
33882        }
33883        if let Some(row_format_after) = &e.row_format_after {
33884            self.write_space();
33885            self.generate_expression(row_format_after)?;
33886        }
33887        if let Some(record_reader) = &e.record_reader {
33888            self.write_space();
33889            self.write_keyword("RECORDREADER");
33890            self.write_space();
33891            self.generate_expression(record_reader)?;
33892        }
33893        Ok(())
33894    }
33895
33896    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
33897        // RANDN([seed])
33898        self.write_keyword("RANDN");
33899        self.write("(");
33900        if let Some(this) = &e.this {
33901            self.generate_expression(this)?;
33902        }
33903        self.write(")");
33904        Ok(())
33905    }
33906
33907    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
33908        // RANDSTR(this, [generator])
33909        self.write_keyword("RANDSTR");
33910        self.write("(");
33911        self.generate_expression(&e.this)?;
33912        if let Some(generator) = &e.generator {
33913            self.write(", ");
33914            self.generate_expression(generator)?;
33915        }
33916        self.write(")");
33917        Ok(())
33918    }
33919
33920    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
33921        // RANGE_BUCKET(this, expression)
33922        self.write_keyword("RANGE_BUCKET");
33923        self.write("(");
33924        self.generate_expression(&e.this)?;
33925        self.write(", ");
33926        self.generate_expression(&e.expression)?;
33927        self.write(")");
33928        Ok(())
33929    }
33930
33931    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
33932        // RANGE_N(this BETWEEN expressions [EACH each])
33933        self.write_keyword("RANGE_N");
33934        self.write("(");
33935        self.generate_expression(&e.this)?;
33936        self.write_space();
33937        self.write_keyword("BETWEEN");
33938        self.write_space();
33939        for (i, expr) in e.expressions.iter().enumerate() {
33940            if i > 0 {
33941                self.write(", ");
33942            }
33943            self.generate_expression(expr)?;
33944        }
33945        if let Some(each) = &e.each {
33946            self.write_space();
33947            self.write_keyword("EACH");
33948            self.write_space();
33949            self.generate_expression(each)?;
33950        }
33951        self.write(")");
33952        Ok(())
33953    }
33954
33955    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
33956        // READ_CSV(this, expressions...)
33957        self.write_keyword("READ_CSV");
33958        self.write("(");
33959        self.generate_expression(&e.this)?;
33960        for expr in &e.expressions {
33961            self.write(", ");
33962            self.generate_expression(expr)?;
33963        }
33964        self.write(")");
33965        Ok(())
33966    }
33967
33968    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
33969        // READ_PARQUET(expressions...)
33970        self.write_keyword("READ_PARQUET");
33971        self.write("(");
33972        for (i, expr) in e.expressions.iter().enumerate() {
33973            if i > 0 {
33974                self.write(", ");
33975            }
33976            self.generate_expression(expr)?;
33977        }
33978        self.write(")");
33979        Ok(())
33980    }
33981
33982    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
33983        // SEARCH kind FIRST BY this SET expression [USING using]
33984        // or CYCLE this SET expression [USING using]
33985        if e.kind == "CYCLE" {
33986            self.write_keyword("CYCLE");
33987        } else {
33988            self.write_keyword("SEARCH");
33989            self.write_space();
33990            self.write(&e.kind);
33991            self.write_space();
33992            self.write_keyword("FIRST BY");
33993        }
33994        self.write_space();
33995        self.generate_expression(&e.this)?;
33996        self.write_space();
33997        self.write_keyword("SET");
33998        self.write_space();
33999        self.generate_expression(&e.expression)?;
34000        if let Some(using) = &e.using {
34001            self.write_space();
34002            self.write_keyword("USING");
34003            self.write_space();
34004            self.generate_expression(using)?;
34005        }
34006        Ok(())
34007    }
34008
34009    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
34010        // REDUCE(this, initial, merge, [finish])
34011        self.write_keyword("REDUCE");
34012        self.write("(");
34013        self.generate_expression(&e.this)?;
34014        if let Some(initial) = &e.initial {
34015            self.write(", ");
34016            self.generate_expression(initial)?;
34017        }
34018        if let Some(merge) = &e.merge {
34019            self.write(", ");
34020            self.generate_expression(merge)?;
34021        }
34022        if let Some(finish) = &e.finish {
34023            self.write(", ");
34024            self.generate_expression(finish)?;
34025        }
34026        self.write(")");
34027        Ok(())
34028    }
34029
34030    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
34031        // REFERENCES this (expressions) [options]
34032        self.write_keyword("REFERENCES");
34033        self.write_space();
34034        self.generate_expression(&e.this)?;
34035        if !e.expressions.is_empty() {
34036            self.write(" (");
34037            for (i, expr) in e.expressions.iter().enumerate() {
34038                if i > 0 {
34039                    self.write(", ");
34040                }
34041                self.generate_expression(expr)?;
34042            }
34043            self.write(")");
34044        }
34045        for opt in &e.options {
34046            self.write_space();
34047            self.generate_expression(opt)?;
34048        }
34049        Ok(())
34050    }
34051
34052    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
34053        // REFRESH [kind] this
34054        self.write_keyword("REFRESH");
34055        if !e.kind.is_empty() {
34056            self.write_space();
34057            self.write_keyword(&e.kind);
34058        }
34059        self.write_space();
34060        self.generate_expression(&e.this)?;
34061        Ok(())
34062    }
34063
34064    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
34065        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
34066        self.write_keyword("REFRESH");
34067        self.write_space();
34068        self.write_keyword(&e.method);
34069
34070        if let Some(ref kind) = e.kind {
34071            self.write_space();
34072            self.write_keyword("ON");
34073            self.write_space();
34074            self.write_keyword(kind);
34075
34076            // EVERY n UNIT
34077            if let Some(ref every) = e.every {
34078                self.write_space();
34079                self.write_keyword("EVERY");
34080                self.write_space();
34081                self.generate_expression(every)?;
34082                if let Some(ref unit) = e.unit {
34083                    self.write_space();
34084                    self.write_keyword(unit);
34085                }
34086            }
34087
34088            // STARTS 'datetime'
34089            if let Some(ref starts) = e.starts {
34090                self.write_space();
34091                self.write_keyword("STARTS");
34092                self.write_space();
34093                self.generate_expression(starts)?;
34094            }
34095        }
34096        Ok(())
34097    }
34098
34099    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
34100        // REGEXP_COUNT(this, expression, position, parameters)
34101        self.write_keyword("REGEXP_COUNT");
34102        self.write("(");
34103        self.generate_expression(&e.this)?;
34104        self.write(", ");
34105        self.generate_expression(&e.expression)?;
34106        if let Some(position) = &e.position {
34107            self.write(", ");
34108            self.generate_expression(position)?;
34109        }
34110        if let Some(parameters) = &e.parameters {
34111            self.write(", ");
34112            self.generate_expression(parameters)?;
34113        }
34114        self.write(")");
34115        Ok(())
34116    }
34117
34118    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
34119        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
34120        self.write_keyword("REGEXP_EXTRACT_ALL");
34121        self.write("(");
34122        self.generate_expression(&e.this)?;
34123        self.write(", ");
34124        self.generate_expression(&e.expression)?;
34125        if let Some(group) = &e.group {
34126            self.write(", ");
34127            self.generate_expression(group)?;
34128        }
34129        self.write(")");
34130        Ok(())
34131    }
34132
34133    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
34134        // REGEXP_FULL_MATCH(this, expression)
34135        self.write_keyword("REGEXP_FULL_MATCH");
34136        self.write("(");
34137        self.generate_expression(&e.this)?;
34138        self.write(", ");
34139        self.generate_expression(&e.expression)?;
34140        self.write(")");
34141        Ok(())
34142    }
34143
34144    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
34145        use crate::dialects::DialectType;
34146        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
34147        if matches!(
34148            self.config.dialect,
34149            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
34150        ) && e.flag.is_none()
34151        {
34152            self.generate_expression(&e.this)?;
34153            self.write(" ~* ");
34154            self.generate_expression(&e.expression)?;
34155        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
34156            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
34157            self.write_keyword("REGEXP_LIKE");
34158            self.write("(");
34159            self.generate_expression(&e.this)?;
34160            self.write(", ");
34161            self.generate_expression(&e.expression)?;
34162            self.write(", ");
34163            if let Some(flag) = &e.flag {
34164                self.generate_expression(flag)?;
34165            } else {
34166                self.write("'i'");
34167            }
34168            self.write(")");
34169        } else {
34170            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
34171            self.generate_expression(&e.this)?;
34172            self.write_space();
34173            self.write_keyword("REGEXP_ILIKE");
34174            self.write_space();
34175            self.generate_expression(&e.expression)?;
34176            if let Some(flag) = &e.flag {
34177                self.write(", ");
34178                self.generate_expression(flag)?;
34179            }
34180        }
34181        Ok(())
34182    }
34183
34184    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
34185        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
34186        self.write_keyword("REGEXP_INSTR");
34187        self.write("(");
34188        self.generate_expression(&e.this)?;
34189        self.write(", ");
34190        self.generate_expression(&e.expression)?;
34191        if let Some(position) = &e.position {
34192            self.write(", ");
34193            self.generate_expression(position)?;
34194        }
34195        if let Some(occurrence) = &e.occurrence {
34196            self.write(", ");
34197            self.generate_expression(occurrence)?;
34198        }
34199        if let Some(option) = &e.option {
34200            self.write(", ");
34201            self.generate_expression(option)?;
34202        }
34203        if let Some(parameters) = &e.parameters {
34204            self.write(", ");
34205            self.generate_expression(parameters)?;
34206        }
34207        if let Some(group) = &e.group {
34208            self.write(", ");
34209            self.generate_expression(group)?;
34210        }
34211        self.write(")");
34212        Ok(())
34213    }
34214
34215    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
34216        // REGEXP_SPLIT(this, expression, limit)
34217        self.write_keyword("REGEXP_SPLIT");
34218        self.write("(");
34219        self.generate_expression(&e.this)?;
34220        self.write(", ");
34221        self.generate_expression(&e.expression)?;
34222        if let Some(limit) = &e.limit {
34223            self.write(", ");
34224            self.generate_expression(limit)?;
34225        }
34226        self.write(")");
34227        Ok(())
34228    }
34229
34230    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
34231        // REGR_AVGX(this, expression)
34232        self.write_keyword("REGR_AVGX");
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_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
34242        // REGR_AVGY(this, expression)
34243        self.write_keyword("REGR_AVGY");
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_count(&mut self, e: &RegrCount) -> Result<()> {
34253        // REGR_COUNT(this, expression)
34254        self.write_keyword("REGR_COUNT");
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_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
34264        // REGR_INTERCEPT(this, expression)
34265        self.write_keyword("REGR_INTERCEPT");
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_r2(&mut self, e: &RegrR2) -> Result<()> {
34275        // REGR_R2(this, expression)
34276        self.write_keyword("REGR_R2");
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_slope(&mut self, e: &RegrSlope) -> Result<()> {
34286        // REGR_SLOPE(this, expression)
34287        self.write_keyword("REGR_SLOPE");
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_sxx(&mut self, e: &RegrSxx) -> Result<()> {
34297        // REGR_SXX(this, expression)
34298        self.write_keyword("REGR_SXX");
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_sxy(&mut self, e: &RegrSxy) -> Result<()> {
34308        // REGR_SXY(this, expression)
34309        self.write_keyword("REGR_SXY");
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_syy(&mut self, e: &RegrSyy) -> Result<()> {
34319        // REGR_SYY(this, expression)
34320        self.write_keyword("REGR_SYY");
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_valx(&mut self, e: &RegrValx) -> Result<()> {
34330        // REGR_VALX(this, expression)
34331        self.write_keyword("REGR_VALX");
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_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
34341        // REGR_VALY(this, expression)
34342        self.write_keyword("REGR_VALY");
34343        self.write("(");
34344        self.generate_expression(&e.this)?;
34345        self.write(", ");
34346        self.generate_expression(&e.expression)?;
34347        self.write(")");
34348        Ok(())
34349    }
34350
34351    fn generate_remote_with_connection_model_property(
34352        &mut self,
34353        e: &RemoteWithConnectionModelProperty,
34354    ) -> Result<()> {
34355        // REMOTE WITH CONNECTION this
34356        self.write_keyword("REMOTE WITH CONNECTION");
34357        self.write_space();
34358        self.generate_expression(&e.this)?;
34359        Ok(())
34360    }
34361
34362    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
34363        // RENAME COLUMN [IF EXISTS] this TO new_name
34364        self.write_keyword("RENAME COLUMN");
34365        if e.exists {
34366            self.write_space();
34367            self.write_keyword("IF EXISTS");
34368        }
34369        self.write_space();
34370        self.generate_expression(&e.this)?;
34371        if let Some(to) = &e.to {
34372            self.write_space();
34373            self.write_keyword("TO");
34374            self.write_space();
34375            self.generate_expression(to)?;
34376        }
34377        Ok(())
34378    }
34379
34380    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
34381        // REPLACE PARTITION expression [FROM source]
34382        self.write_keyword("REPLACE PARTITION");
34383        self.write_space();
34384        self.generate_expression(&e.expression)?;
34385        if let Some(source) = &e.source {
34386            self.write_space();
34387            self.write_keyword("FROM");
34388            self.write_space();
34389            self.generate_expression(source)?;
34390        }
34391        Ok(())
34392    }
34393
34394    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
34395        // RETURNING expressions [INTO into]
34396        // TSQL and Fabric use OUTPUT instead of RETURNING
34397        let keyword = match self.config.dialect {
34398            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
34399            _ => "RETURNING",
34400        };
34401        self.write_keyword(keyword);
34402        self.write_space();
34403        for (i, expr) in e.expressions.iter().enumerate() {
34404            if i > 0 {
34405                self.write(", ");
34406            }
34407            self.generate_expression(expr)?;
34408        }
34409        if let Some(into) = &e.into {
34410            self.write_space();
34411            self.write_keyword("INTO");
34412            self.write_space();
34413            self.generate_expression(into)?;
34414        }
34415        Ok(())
34416    }
34417
34418    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
34419        // OUTPUT expressions [INTO into_table]
34420        self.write_space();
34421        self.write_keyword("OUTPUT");
34422        self.write_space();
34423        for (i, expr) in output.columns.iter().enumerate() {
34424            if i > 0 {
34425                self.write(", ");
34426            }
34427            self.generate_expression(expr)?;
34428        }
34429        if let Some(into_table) = &output.into_table {
34430            self.write_space();
34431            self.write_keyword("INTO");
34432            self.write_space();
34433            self.generate_expression(into_table)?;
34434        }
34435        Ok(())
34436    }
34437
34438    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
34439        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
34440        self.write_keyword("RETURNS");
34441        if e.is_table.is_some() {
34442            self.write_space();
34443            self.write_keyword("TABLE");
34444        }
34445        if let Some(table) = &e.table {
34446            self.write_space();
34447            self.generate_expression(table)?;
34448        } else if let Some(this) = &e.this {
34449            self.write_space();
34450            self.generate_expression(this)?;
34451        }
34452        if e.null.is_some() {
34453            self.write_space();
34454            self.write_keyword("NULL ON NULL INPUT");
34455        }
34456        Ok(())
34457    }
34458
34459    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
34460        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
34461        self.write_keyword("ROLLBACK");
34462
34463        // TSQL always uses ROLLBACK TRANSACTION
34464        if e.this.is_none()
34465            && matches!(
34466                self.config.dialect,
34467                Some(DialectType::TSQL) | Some(DialectType::Fabric)
34468            )
34469        {
34470            self.write_space();
34471            self.write_keyword("TRANSACTION");
34472        }
34473
34474        // Check if this has TRANSACTION keyword or transaction name
34475        if let Some(this) = &e.this {
34476            // Check if it's just the "TRANSACTION" marker or an actual transaction name
34477            let is_transaction_marker = matches!(
34478                this.as_ref(),
34479                Expression::Identifier(id) if id.name == "TRANSACTION"
34480            );
34481
34482            self.write_space();
34483            self.write_keyword("TRANSACTION");
34484
34485            // If it's a real transaction name, output it
34486            if !is_transaction_marker {
34487                self.write_space();
34488                self.generate_expression(this)?;
34489            }
34490        }
34491
34492        // Output TO savepoint
34493        if let Some(savepoint) = &e.savepoint {
34494            self.write_space();
34495            self.write_keyword("TO");
34496            self.write_space();
34497            self.generate_expression(savepoint)?;
34498        }
34499        Ok(())
34500    }
34501
34502    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
34503        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
34504        if e.expressions.is_empty() {
34505            self.write_keyword("WITH ROLLUP");
34506        } else {
34507            self.write_keyword("ROLLUP");
34508            self.write("(");
34509            for (i, expr) in e.expressions.iter().enumerate() {
34510                if i > 0 {
34511                    self.write(", ");
34512                }
34513                self.generate_expression(expr)?;
34514            }
34515            self.write(")");
34516        }
34517        Ok(())
34518    }
34519
34520    fn generate_row_format_delimited_property(
34521        &mut self,
34522        e: &RowFormatDelimitedProperty,
34523    ) -> Result<()> {
34524        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
34525        self.write_keyword("ROW FORMAT DELIMITED");
34526        if let Some(fields) = &e.fields {
34527            self.write_space();
34528            self.write_keyword("FIELDS TERMINATED BY");
34529            self.write_space();
34530            self.generate_expression(fields)?;
34531        }
34532        if let Some(escaped) = &e.escaped {
34533            self.write_space();
34534            self.write_keyword("ESCAPED BY");
34535            self.write_space();
34536            self.generate_expression(escaped)?;
34537        }
34538        if let Some(items) = &e.collection_items {
34539            self.write_space();
34540            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
34541            self.write_space();
34542            self.generate_expression(items)?;
34543        }
34544        if let Some(keys) = &e.map_keys {
34545            self.write_space();
34546            self.write_keyword("MAP KEYS TERMINATED BY");
34547            self.write_space();
34548            self.generate_expression(keys)?;
34549        }
34550        if let Some(lines) = &e.lines {
34551            self.write_space();
34552            self.write_keyword("LINES TERMINATED BY");
34553            self.write_space();
34554            self.generate_expression(lines)?;
34555        }
34556        if let Some(null) = &e.null {
34557            self.write_space();
34558            self.write_keyword("NULL DEFINED AS");
34559            self.write_space();
34560            self.generate_expression(null)?;
34561        }
34562        if let Some(serde) = &e.serde {
34563            self.write_space();
34564            self.generate_expression(serde)?;
34565        }
34566        Ok(())
34567    }
34568
34569    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
34570        // ROW FORMAT this
34571        self.write_keyword("ROW FORMAT");
34572        self.write_space();
34573        self.generate_expression(&e.this)?;
34574        Ok(())
34575    }
34576
34577    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
34578        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
34579        self.write_keyword("ROW FORMAT SERDE");
34580        self.write_space();
34581        self.generate_expression(&e.this)?;
34582        if let Some(props) = &e.serde_properties {
34583            self.write_space();
34584            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
34585            self.generate_expression(props)?;
34586        }
34587        Ok(())
34588    }
34589
34590    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
34591        // SHA2(this, length)
34592        self.write_keyword("SHA2");
34593        self.write("(");
34594        self.generate_expression(&e.this)?;
34595        if let Some(length) = e.length {
34596            self.write(", ");
34597            self.write(&length.to_string());
34598        }
34599        self.write(")");
34600        Ok(())
34601    }
34602
34603    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
34604        // SHA2_DIGEST(this, length)
34605        self.write_keyword("SHA2_DIGEST");
34606        self.write("(");
34607        self.generate_expression(&e.this)?;
34608        if let Some(length) = e.length {
34609            self.write(", ");
34610            self.write(&length.to_string());
34611        }
34612        self.write(")");
34613        Ok(())
34614    }
34615
34616    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
34617        let name = if matches!(
34618            self.config.dialect,
34619            Some(crate::dialects::DialectType::Spark)
34620                | Some(crate::dialects::DialectType::Databricks)
34621        ) {
34622            "TRY_ADD"
34623        } else {
34624            "SAFE_ADD"
34625        };
34626        self.write_keyword(name);
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_divide(&mut self, e: &SafeDivide) -> Result<()> {
34636        // SAFE_DIVIDE(this, expression)
34637        self.write_keyword("SAFE_DIVIDE");
34638        self.write("(");
34639        self.generate_expression(&e.this)?;
34640        self.write(", ");
34641        self.generate_expression(&e.expression)?;
34642        self.write(")");
34643        Ok(())
34644    }
34645
34646    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
34647        let name = if matches!(
34648            self.config.dialect,
34649            Some(crate::dialects::DialectType::Spark)
34650                | Some(crate::dialects::DialectType::Databricks)
34651        ) {
34652            "TRY_MULTIPLY"
34653        } else {
34654            "SAFE_MULTIPLY"
34655        };
34656        self.write_keyword(name);
34657        self.write("(");
34658        self.generate_expression(&e.this)?;
34659        self.write(", ");
34660        self.generate_expression(&e.expression)?;
34661        self.write(")");
34662        Ok(())
34663    }
34664
34665    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
34666        let name = if matches!(
34667            self.config.dialect,
34668            Some(crate::dialects::DialectType::Spark)
34669                | Some(crate::dialects::DialectType::Databricks)
34670        ) {
34671            "TRY_SUBTRACT"
34672        } else {
34673            "SAFE_SUBTRACT"
34674        };
34675        self.write_keyword(name);
34676        self.write("(");
34677        self.generate_expression(&e.this)?;
34678        self.write(", ");
34679        self.generate_expression(&e.expression)?;
34680        self.write(")");
34681        Ok(())
34682    }
34683
34684    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
34685    /// METHOD (size UNIT) [REPEATABLE (seed)]
34686    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
34687        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
34688        if matches!(sample.method, SampleMethod::Bucket) {
34689            self.write(" (");
34690            self.write_keyword("BUCKET");
34691            self.write_space();
34692            if let Some(ref num) = sample.bucket_numerator {
34693                self.generate_expression(num)?;
34694            }
34695            self.write_space();
34696            self.write_keyword("OUT OF");
34697            self.write_space();
34698            if let Some(ref denom) = sample.bucket_denominator {
34699                self.generate_expression(denom)?;
34700            }
34701            if let Some(ref field) = sample.bucket_field {
34702                self.write_space();
34703                self.write_keyword("ON");
34704                self.write_space();
34705                self.generate_expression(field)?;
34706            }
34707            self.write(")");
34708            return Ok(());
34709        }
34710
34711        // Output method name if explicitly specified, or for dialects that always require it
34712        let is_snowflake = matches!(
34713            self.config.dialect,
34714            Some(crate::dialects::DialectType::Snowflake)
34715        );
34716        let is_postgres = matches!(
34717            self.config.dialect,
34718            Some(crate::dialects::DialectType::PostgreSQL)
34719                | Some(crate::dialects::DialectType::Redshift)
34720        );
34721        // Databricks and Spark don't output method names
34722        let is_databricks = matches!(
34723            self.config.dialect,
34724            Some(crate::dialects::DialectType::Databricks)
34725        );
34726        let is_spark = matches!(
34727            self.config.dialect,
34728            Some(crate::dialects::DialectType::Spark)
34729        );
34730        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
34731        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
34732        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
34733        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
34734            self.write_space();
34735            if !sample.explicit_method && (is_snowflake || force_method) {
34736                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
34737                self.write_keyword("BERNOULLI");
34738            } else {
34739                match sample.method {
34740                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
34741                    SampleMethod::System => self.write_keyword("SYSTEM"),
34742                    SampleMethod::Block => self.write_keyword("BLOCK"),
34743                    SampleMethod::Row => self.write_keyword("ROW"),
34744                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
34745                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
34746                    SampleMethod::Bucket => {} // handled above
34747                }
34748            }
34749        }
34750
34751        // Output size, with or without parentheses depending on dialect
34752        let emit_size_no_parens = !self.config.tablesample_requires_parens;
34753        if emit_size_no_parens {
34754            self.write_space();
34755            match &sample.size {
34756                Expression::Tuple(tuple) => {
34757                    for (i, expr) in tuple.expressions.iter().enumerate() {
34758                        if i > 0 {
34759                            self.write(", ");
34760                        }
34761                        self.generate_expression(expr)?;
34762                    }
34763                }
34764                expr => self.generate_expression(expr)?,
34765            }
34766        } else {
34767            self.write(" (");
34768            self.generate_expression(&sample.size)?;
34769        }
34770
34771        // Determine unit
34772        let is_rows_method = matches!(
34773            sample.method,
34774            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
34775        );
34776        let is_percent = matches!(
34777            sample.method,
34778            SampleMethod::Percent
34779                | SampleMethod::System
34780                | SampleMethod::Bernoulli
34781                | SampleMethod::Block
34782        );
34783
34784        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
34785        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
34786        // For Databricks and Spark, always output PERCENT for percentage samples.
34787        let is_presto = matches!(
34788            self.config.dialect,
34789            Some(crate::dialects::DialectType::Presto)
34790                | Some(crate::dialects::DialectType::Trino)
34791                | Some(crate::dialects::DialectType::Athena)
34792        );
34793        let should_output_unit = if is_databricks || is_spark {
34794            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
34795            is_percent || is_rows_method || sample.unit_after_size
34796        } else if is_snowflake || is_postgres || is_presto {
34797            sample.unit_after_size
34798        } else {
34799            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
34800        };
34801
34802        if should_output_unit {
34803            self.write_space();
34804            if sample.is_percent {
34805                self.write_keyword("PERCENT");
34806            } else if is_rows_method && !sample.unit_after_size {
34807                self.write_keyword("ROWS");
34808            } else if sample.unit_after_size {
34809                match sample.method {
34810                    SampleMethod::Percent
34811                    | SampleMethod::System
34812                    | SampleMethod::Bernoulli
34813                    | SampleMethod::Block => {
34814                        self.write_keyword("PERCENT");
34815                    }
34816                    SampleMethod::Row | SampleMethod::Reservoir => {
34817                        self.write_keyword("ROWS");
34818                    }
34819                    _ => self.write_keyword("ROWS"),
34820                }
34821            } else {
34822                self.write_keyword("PERCENT");
34823            }
34824        }
34825
34826        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34827            if let Some(ref offset) = sample.offset {
34828                self.write_space();
34829                self.write_keyword("OFFSET");
34830                self.write_space();
34831                self.generate_expression(offset)?;
34832            }
34833        }
34834        if !emit_size_no_parens {
34835            self.write(")");
34836        }
34837
34838        Ok(())
34839    }
34840
34841    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
34842        // SAMPLE this (ClickHouse uses SAMPLE BY)
34843        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34844            self.write_keyword("SAMPLE BY");
34845        } else {
34846            self.write_keyword("SAMPLE");
34847        }
34848        self.write_space();
34849        self.generate_expression(&e.this)?;
34850        Ok(())
34851    }
34852
34853    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
34854        // this (expressions...)
34855        if let Some(this) = &e.this {
34856            self.generate_expression(this)?;
34857        }
34858        if !e.expressions.is_empty() {
34859            // Add space before column list if there's a preceding expression
34860            if e.this.is_some() {
34861                self.write_space();
34862            }
34863            self.write("(");
34864            for (i, expr) in e.expressions.iter().enumerate() {
34865                if i > 0 {
34866                    self.write(", ");
34867                }
34868                self.generate_expression(expr)?;
34869            }
34870            self.write(")");
34871        }
34872        Ok(())
34873    }
34874
34875    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
34876        // COMMENT this
34877        self.write_keyword("COMMENT");
34878        self.write_space();
34879        self.generate_expression(&e.this)?;
34880        Ok(())
34881    }
34882
34883    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
34884        // [this::]expression
34885        if let Some(this) = &e.this {
34886            self.generate_expression(this)?;
34887            self.write("::");
34888        }
34889        self.generate_expression(&e.expression)?;
34890        Ok(())
34891    }
34892
34893    fn generate_search(&mut self, e: &Search) -> Result<()> {
34894        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
34895        self.write_keyword("SEARCH");
34896        self.write("(");
34897        self.generate_expression(&e.this)?;
34898        self.write(", ");
34899        self.generate_expression(&e.expression)?;
34900        if let Some(json_scope) = &e.json_scope {
34901            self.write(", ");
34902            self.generate_expression(json_scope)?;
34903        }
34904        if let Some(analyzer) = &e.analyzer {
34905            self.write(", ");
34906            self.generate_expression(analyzer)?;
34907        }
34908        if let Some(analyzer_options) = &e.analyzer_options {
34909            self.write(", ");
34910            self.generate_expression(analyzer_options)?;
34911        }
34912        if let Some(search_mode) = &e.search_mode {
34913            self.write(", ");
34914            self.generate_expression(search_mode)?;
34915        }
34916        self.write(")");
34917        Ok(())
34918    }
34919
34920    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
34921        // SEARCH_IP(this, expression)
34922        self.write_keyword("SEARCH_IP");
34923        self.write("(");
34924        self.generate_expression(&e.this)?;
34925        self.write(", ");
34926        self.generate_expression(&e.expression)?;
34927        self.write(")");
34928        Ok(())
34929    }
34930
34931    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
34932        // SECURITY this
34933        self.write_keyword("SECURITY");
34934        self.write_space();
34935        self.generate_expression(&e.this)?;
34936        Ok(())
34937    }
34938
34939    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
34940        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
34941        self.write("SEMANTIC_VIEW(");
34942
34943        if self.config.pretty {
34944            // Pretty print: each clause on its own line
34945            self.write_newline();
34946            self.indent_level += 1;
34947            self.write_indent();
34948            self.generate_expression(&e.this)?;
34949
34950            if let Some(metrics) = &e.metrics {
34951                self.write_newline();
34952                self.write_indent();
34953                self.write_keyword("METRICS");
34954                self.write_space();
34955                self.generate_semantic_view_tuple(metrics)?;
34956            }
34957            if let Some(dimensions) = &e.dimensions {
34958                self.write_newline();
34959                self.write_indent();
34960                self.write_keyword("DIMENSIONS");
34961                self.write_space();
34962                self.generate_semantic_view_tuple(dimensions)?;
34963            }
34964            if let Some(facts) = &e.facts {
34965                self.write_newline();
34966                self.write_indent();
34967                self.write_keyword("FACTS");
34968                self.write_space();
34969                self.generate_semantic_view_tuple(facts)?;
34970            }
34971            if let Some(where_) = &e.where_ {
34972                self.write_newline();
34973                self.write_indent();
34974                self.write_keyword("WHERE");
34975                self.write_space();
34976                self.generate_expression(where_)?;
34977            }
34978            self.write_newline();
34979            self.indent_level -= 1;
34980            self.write_indent();
34981        } else {
34982            // Compact: all on one line
34983            self.generate_expression(&e.this)?;
34984            if let Some(metrics) = &e.metrics {
34985                self.write_space();
34986                self.write_keyword("METRICS");
34987                self.write_space();
34988                self.generate_semantic_view_tuple(metrics)?;
34989            }
34990            if let Some(dimensions) = &e.dimensions {
34991                self.write_space();
34992                self.write_keyword("DIMENSIONS");
34993                self.write_space();
34994                self.generate_semantic_view_tuple(dimensions)?;
34995            }
34996            if let Some(facts) = &e.facts {
34997                self.write_space();
34998                self.write_keyword("FACTS");
34999                self.write_space();
35000                self.generate_semantic_view_tuple(facts)?;
35001            }
35002            if let Some(where_) = &e.where_ {
35003                self.write_space();
35004                self.write_keyword("WHERE");
35005                self.write_space();
35006                self.generate_expression(where_)?;
35007            }
35008        }
35009        self.write(")");
35010        Ok(())
35011    }
35012
35013    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
35014    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
35015        if let Expression::Tuple(t) = expr {
35016            for (i, e) in t.expressions.iter().enumerate() {
35017                if i > 0 {
35018                    self.write(", ");
35019                }
35020                self.generate_expression(e)?;
35021            }
35022        } else {
35023            self.generate_expression(expr)?;
35024        }
35025        Ok(())
35026    }
35027
35028    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
35029        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
35030        if let Some(start) = &e.start {
35031            self.write_keyword("START WITH");
35032            self.write_space();
35033            self.generate_expression(start)?;
35034        }
35035        if let Some(increment) = &e.increment {
35036            self.write_space();
35037            self.write_keyword("INCREMENT BY");
35038            self.write_space();
35039            self.generate_expression(increment)?;
35040        }
35041        if let Some(minvalue) = &e.minvalue {
35042            self.write_space();
35043            self.write_keyword("MINVALUE");
35044            self.write_space();
35045            self.generate_expression(minvalue)?;
35046        }
35047        if let Some(maxvalue) = &e.maxvalue {
35048            self.write_space();
35049            self.write_keyword("MAXVALUE");
35050            self.write_space();
35051            self.generate_expression(maxvalue)?;
35052        }
35053        if let Some(cache) = &e.cache {
35054            self.write_space();
35055            self.write_keyword("CACHE");
35056            self.write_space();
35057            self.generate_expression(cache)?;
35058        }
35059        if let Some(owned) = &e.owned {
35060            self.write_space();
35061            self.write_keyword("OWNED BY");
35062            self.write_space();
35063            self.generate_expression(owned)?;
35064        }
35065        for opt in &e.options {
35066            self.write_space();
35067            self.generate_expression(opt)?;
35068        }
35069        Ok(())
35070    }
35071
35072    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
35073        // [WITH] SERDEPROPERTIES (expressions)
35074        if e.with_.is_some() {
35075            self.write_keyword("WITH");
35076            self.write_space();
35077        }
35078        self.write_keyword("SERDEPROPERTIES");
35079        self.write(" (");
35080        for (i, expr) in e.expressions.iter().enumerate() {
35081            if i > 0 {
35082                self.write(", ");
35083            }
35084            // Generate key=value without spaces around =
35085            match expr {
35086                Expression::Eq(eq) => {
35087                    self.generate_expression(&eq.left)?;
35088                    self.write("=");
35089                    self.generate_expression(&eq.right)?;
35090                }
35091                _ => self.generate_expression(expr)?,
35092            }
35093        }
35094        self.write(")");
35095        Ok(())
35096    }
35097
35098    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
35099        // @@[kind.]this
35100        self.write("@@");
35101        if let Some(kind) = &e.kind {
35102            self.write(kind);
35103            self.write(".");
35104        }
35105        self.generate_expression(&e.this)?;
35106        Ok(())
35107    }
35108
35109    fn generate_set(&mut self, e: &Set) -> Result<()> {
35110        // SET/UNSET [TAG] expressions
35111        if e.unset.is_some() {
35112            self.write_keyword("UNSET");
35113        } else {
35114            self.write_keyword("SET");
35115        }
35116        if e.tag.is_some() {
35117            self.write_space();
35118            self.write_keyword("TAG");
35119        }
35120        if !e.expressions.is_empty() {
35121            self.write_space();
35122            for (i, expr) in e.expressions.iter().enumerate() {
35123                if i > 0 {
35124                    self.write(", ");
35125                }
35126                self.generate_expression(expr)?;
35127            }
35128        }
35129        Ok(())
35130    }
35131
35132    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
35133        // SET this or SETCONFIG this
35134        self.write_keyword("SET");
35135        self.write_space();
35136        self.generate_expression(&e.this)?;
35137        Ok(())
35138    }
35139
35140    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
35141        // [kind] name = value
35142        if let Some(kind) = &e.kind {
35143            self.write_keyword(kind);
35144            self.write_space();
35145        }
35146        self.generate_expression(&e.name)?;
35147        self.write(" = ");
35148        self.generate_expression(&e.value)?;
35149        Ok(())
35150    }
35151
35152    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
35153        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
35154        if let Some(with_) = &e.with_ {
35155            self.generate_expression(with_)?;
35156            self.write_space();
35157        }
35158        self.generate_expression(&e.this)?;
35159        self.write_space();
35160        // kind should be UNION, INTERSECT, EXCEPT, etc.
35161        if let Some(kind) = &e.kind {
35162            self.write_keyword(kind);
35163        }
35164        if e.distinct {
35165            self.write_space();
35166            self.write_keyword("DISTINCT");
35167        } else {
35168            self.write_space();
35169            self.write_keyword("ALL");
35170        }
35171        if e.by_name.is_some() {
35172            self.write_space();
35173            self.write_keyword("BY NAME");
35174        }
35175        self.write_space();
35176        self.generate_expression(&e.expression)?;
35177        Ok(())
35178    }
35179
35180    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
35181        // SET or MULTISET
35182        if e.multi.is_some() {
35183            self.write_keyword("MULTISET");
35184        } else {
35185            self.write_keyword("SET");
35186        }
35187        Ok(())
35188    }
35189
35190    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
35191        // SETTINGS expressions
35192        self.write_keyword("SETTINGS");
35193        if self.config.pretty && e.expressions.len() > 1 {
35194            // Pretty print: each setting on its own line, indented
35195            self.indent_level += 1;
35196            for (i, expr) in e.expressions.iter().enumerate() {
35197                if i > 0 {
35198                    self.write(",");
35199                }
35200                self.write_newline();
35201                self.write_indent();
35202                self.generate_expression(expr)?;
35203            }
35204            self.indent_level -= 1;
35205        } else {
35206            self.write_space();
35207            for (i, expr) in e.expressions.iter().enumerate() {
35208                if i > 0 {
35209                    self.write(", ");
35210                }
35211                self.generate_expression(expr)?;
35212            }
35213        }
35214        Ok(())
35215    }
35216
35217    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
35218        // SHARING = this
35219        self.write_keyword("SHARING");
35220        if let Some(this) = &e.this {
35221            self.write(" = ");
35222            self.generate_expression(this)?;
35223        }
35224        Ok(())
35225    }
35226
35227    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
35228        // Python array slicing: begin:end:step
35229        if let Some(begin) = &e.this {
35230            self.generate_expression(begin)?;
35231        }
35232        self.write(":");
35233        if let Some(end) = &e.expression {
35234            self.generate_expression(end)?;
35235        }
35236        if let Some(step) = &e.step {
35237            self.write(":");
35238            self.generate_expression(step)?;
35239        }
35240        Ok(())
35241    }
35242
35243    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
35244        // SORT_ARRAY(this, asc)
35245        self.write_keyword("SORT_ARRAY");
35246        self.write("(");
35247        self.generate_expression(&e.this)?;
35248        if let Some(asc) = &e.asc {
35249            self.write(", ");
35250            self.generate_expression(asc)?;
35251        }
35252        self.write(")");
35253        Ok(())
35254    }
35255
35256    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
35257        // SORT BY expressions
35258        self.write_keyword("SORT BY");
35259        self.write_space();
35260        for (i, expr) in e.expressions.iter().enumerate() {
35261            if i > 0 {
35262                self.write(", ");
35263            }
35264            self.generate_ordered(expr)?;
35265        }
35266        Ok(())
35267    }
35268
35269    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
35270        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
35271        if e.compound.is_some() {
35272            self.write_keyword("COMPOUND");
35273            self.write_space();
35274        }
35275        self.write_keyword("SORTKEY");
35276        self.write("(");
35277        // If this is a Tuple, unwrap its contents to avoid double parentheses
35278        if let Expression::Tuple(t) = e.this.as_ref() {
35279            for (i, expr) in t.expressions.iter().enumerate() {
35280                if i > 0 {
35281                    self.write(", ");
35282                }
35283                self.generate_expression(expr)?;
35284            }
35285        } else {
35286            self.generate_expression(&e.this)?;
35287        }
35288        self.write(")");
35289        Ok(())
35290    }
35291
35292    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
35293        // SPLIT_PART(this, delimiter, part_index)
35294        self.write_keyword("SPLIT_PART");
35295        self.write("(");
35296        self.generate_expression(&e.this)?;
35297        if let Some(delimiter) = &e.delimiter {
35298            self.write(", ");
35299            self.generate_expression(delimiter)?;
35300        }
35301        if let Some(part_index) = &e.part_index {
35302            self.write(", ");
35303            self.generate_expression(part_index)?;
35304        }
35305        self.write(")");
35306        Ok(())
35307    }
35308
35309    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
35310        // READS SQL DATA or MODIFIES SQL DATA, etc.
35311        self.generate_expression(&e.this)?;
35312        Ok(())
35313    }
35314
35315    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
35316        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
35317        self.write_keyword("SQL SECURITY");
35318        self.write_space();
35319        self.generate_expression(&e.this)?;
35320        Ok(())
35321    }
35322
35323    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
35324        // ST_DISTANCE(this, expression, [use_spheroid])
35325        self.write_keyword("ST_DISTANCE");
35326        self.write("(");
35327        self.generate_expression(&e.this)?;
35328        self.write(", ");
35329        self.generate_expression(&e.expression)?;
35330        if let Some(use_spheroid) = &e.use_spheroid {
35331            self.write(", ");
35332            self.generate_expression(use_spheroid)?;
35333        }
35334        self.write(")");
35335        Ok(())
35336    }
35337
35338    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
35339        // ST_POINT(this, expression)
35340        self.write_keyword("ST_POINT");
35341        self.write("(");
35342        self.generate_expression(&e.this)?;
35343        self.write(", ");
35344        self.generate_expression(&e.expression)?;
35345        self.write(")");
35346        Ok(())
35347    }
35348
35349    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
35350        // IMMUTABLE, STABLE, VOLATILE
35351        self.generate_expression(&e.this)?;
35352        Ok(())
35353    }
35354
35355    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
35356        // STANDARD_HASH(this, [expression])
35357        self.write_keyword("STANDARD_HASH");
35358        self.write("(");
35359        self.generate_expression(&e.this)?;
35360        if let Some(expression) = &e.expression {
35361            self.write(", ");
35362            self.generate_expression(expression)?;
35363        }
35364        self.write(")");
35365        Ok(())
35366    }
35367
35368    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
35369        // STORED BY this
35370        self.write_keyword("STORED BY");
35371        self.write_space();
35372        self.generate_expression(&e.this)?;
35373        Ok(())
35374    }
35375
35376    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
35377        // STRPOS(this, substr) or STRPOS(this, substr, position)
35378        // Different dialects have different function names
35379        use crate::dialects::DialectType;
35380        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35381            // Snowflake: CHARINDEX(substr, str[, position])
35382            self.write_keyword("CHARINDEX");
35383            self.write("(");
35384            if let Some(substr) = &e.substr {
35385                self.generate_expression(substr)?;
35386                self.write(", ");
35387            }
35388            self.generate_expression(&e.this)?;
35389            if let Some(position) = &e.position {
35390                self.write(", ");
35391                self.generate_expression(position)?;
35392            }
35393            self.write(")");
35394        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
35395            self.write_keyword("POSITION");
35396            self.write("(");
35397            self.generate_expression(&e.this)?;
35398            if let Some(substr) = &e.substr {
35399                self.write(", ");
35400                self.generate_expression(substr)?;
35401            }
35402            if let Some(position) = &e.position {
35403                self.write(", ");
35404                self.generate_expression(position)?;
35405            }
35406            if let Some(occurrence) = &e.occurrence {
35407                self.write(", ");
35408                self.generate_expression(occurrence)?;
35409            }
35410            self.write(")");
35411        } else if matches!(
35412            self.config.dialect,
35413            Some(DialectType::SQLite)
35414                | Some(DialectType::Oracle)
35415                | Some(DialectType::BigQuery)
35416                | Some(DialectType::Teradata)
35417        ) {
35418            self.write_keyword("INSTR");
35419            self.write("(");
35420            self.generate_expression(&e.this)?;
35421            if let Some(substr) = &e.substr {
35422                self.write(", ");
35423                self.generate_expression(substr)?;
35424            }
35425            if let Some(position) = &e.position {
35426                self.write(", ");
35427                self.generate_expression(position)?;
35428            } else if e.occurrence.is_some() {
35429                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
35430                // Default start position is 1
35431                self.write(", 1");
35432            }
35433            if let Some(occurrence) = &e.occurrence {
35434                self.write(", ");
35435                self.generate_expression(occurrence)?;
35436            }
35437            self.write(")");
35438        } else if matches!(
35439            self.config.dialect,
35440            Some(DialectType::MySQL)
35441                | Some(DialectType::SingleStore)
35442                | Some(DialectType::Doris)
35443                | Some(DialectType::StarRocks)
35444                | Some(DialectType::Hive)
35445                | Some(DialectType::Spark)
35446                | Some(DialectType::Databricks)
35447        ) {
35448            // LOCATE(substr, str[, position]) - substr first
35449            self.write_keyword("LOCATE");
35450            self.write("(");
35451            if let Some(substr) = &e.substr {
35452                self.generate_expression(substr)?;
35453                self.write(", ");
35454            }
35455            self.generate_expression(&e.this)?;
35456            if let Some(position) = &e.position {
35457                self.write(", ");
35458                self.generate_expression(position)?;
35459            }
35460            self.write(")");
35461        } else if matches!(
35462            self.config.dialect,
35463            Some(DialectType::TSQL) | Some(DialectType::Fabric)
35464        ) {
35465            // CHARINDEX(substr, str[, position])
35466            self.write_keyword("CHARINDEX");
35467            self.write("(");
35468            if let Some(substr) = &e.substr {
35469                self.generate_expression(substr)?;
35470                self.write(", ");
35471            }
35472            self.generate_expression(&e.this)?;
35473            if let Some(position) = &e.position {
35474                self.write(", ");
35475                self.generate_expression(position)?;
35476            }
35477            self.write(")");
35478        } else if matches!(
35479            self.config.dialect,
35480            Some(DialectType::PostgreSQL)
35481                | Some(DialectType::Materialize)
35482                | Some(DialectType::RisingWave)
35483                | Some(DialectType::Redshift)
35484        ) {
35485            // POSITION(substr IN str) syntax
35486            self.write_keyword("POSITION");
35487            self.write("(");
35488            if let Some(substr) = &e.substr {
35489                self.generate_expression(substr)?;
35490                self.write(" IN ");
35491            }
35492            self.generate_expression(&e.this)?;
35493            self.write(")");
35494        } else {
35495            self.write_keyword("STRPOS");
35496            self.write("(");
35497            self.generate_expression(&e.this)?;
35498            if let Some(substr) = &e.substr {
35499                self.write(", ");
35500                self.generate_expression(substr)?;
35501            }
35502            if let Some(position) = &e.position {
35503                self.write(", ");
35504                self.generate_expression(position)?;
35505            }
35506            if let Some(occurrence) = &e.occurrence {
35507                self.write(", ");
35508                self.generate_expression(occurrence)?;
35509            }
35510            self.write(")");
35511        }
35512        Ok(())
35513    }
35514
35515    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
35516        match self.config.dialect {
35517            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
35518                // TO_DATE(this, java_format)
35519                self.write_keyword("TO_DATE");
35520                self.write("(");
35521                self.generate_expression(&e.this)?;
35522                if let Some(format) = &e.format {
35523                    self.write(", '");
35524                    self.write(&Self::strftime_to_java_format(format));
35525                    self.write("'");
35526                }
35527                self.write(")");
35528            }
35529            Some(DialectType::DuckDB) => {
35530                // CAST(STRPTIME(this, format) AS DATE)
35531                self.write_keyword("CAST");
35532                self.write("(");
35533                self.write_keyword("STRPTIME");
35534                self.write("(");
35535                self.generate_expression(&e.this)?;
35536                if let Some(format) = &e.format {
35537                    self.write(", '");
35538                    self.write(format);
35539                    self.write("'");
35540                }
35541                self.write(")");
35542                self.write_keyword(" AS ");
35543                self.write_keyword("DATE");
35544                self.write(")");
35545            }
35546            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
35547                // TO_DATE(this, pg_format)
35548                self.write_keyword("TO_DATE");
35549                self.write("(");
35550                self.generate_expression(&e.this)?;
35551                if let Some(format) = &e.format {
35552                    self.write(", '");
35553                    self.write(&Self::strftime_to_postgres_format(format));
35554                    self.write("'");
35555                }
35556                self.write(")");
35557            }
35558            Some(DialectType::BigQuery) => {
35559                // PARSE_DATE(format, this) - note: format comes first for BigQuery
35560                self.write_keyword("PARSE_DATE");
35561                self.write("(");
35562                if let Some(format) = &e.format {
35563                    self.write("'");
35564                    self.write(format);
35565                    self.write("'");
35566                    self.write(", ");
35567                }
35568                self.generate_expression(&e.this)?;
35569                self.write(")");
35570            }
35571            Some(DialectType::Teradata) => {
35572                // CAST(this AS DATE FORMAT 'teradata_fmt')
35573                self.write_keyword("CAST");
35574                self.write("(");
35575                self.generate_expression(&e.this)?;
35576                self.write_keyword(" AS ");
35577                self.write_keyword("DATE");
35578                if let Some(format) = &e.format {
35579                    self.write_keyword(" FORMAT ");
35580                    self.write("'");
35581                    self.write(&Self::strftime_to_teradata_format(format));
35582                    self.write("'");
35583                }
35584                self.write(")");
35585            }
35586            _ => {
35587                // STR_TO_DATE(this, format) - MySQL default
35588                self.write_keyword("STR_TO_DATE");
35589                self.write("(");
35590                self.generate_expression(&e.this)?;
35591                if let Some(format) = &e.format {
35592                    self.write(", '");
35593                    self.write(format);
35594                    self.write("'");
35595                }
35596                self.write(")");
35597            }
35598        }
35599        Ok(())
35600    }
35601
35602    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
35603    fn strftime_to_teradata_format(fmt: &str) -> String {
35604        let mut result = String::with_capacity(fmt.len() * 2);
35605        let bytes = fmt.as_bytes();
35606        let len = bytes.len();
35607        let mut i = 0;
35608        while i < len {
35609            if bytes[i] == b'%' && i + 1 < len {
35610                let replacement = match bytes[i + 1] {
35611                    b'Y' => "YYYY",
35612                    b'y' => "YY",
35613                    b'm' => "MM",
35614                    b'B' => "MMMM",
35615                    b'b' => "MMM",
35616                    b'd' => "DD",
35617                    b'j' => "DDD",
35618                    b'H' => "HH",
35619                    b'M' => "MI",
35620                    b'S' => "SS",
35621                    b'f' => "SSSSSS",
35622                    b'A' => "EEEE",
35623                    b'a' => "EEE",
35624                    _ => {
35625                        result.push('%');
35626                        i += 1;
35627                        continue;
35628                    }
35629                };
35630                result.push_str(replacement);
35631                i += 2;
35632            } else {
35633                result.push(bytes[i] as char);
35634                i += 1;
35635            }
35636        }
35637        result
35638    }
35639
35640    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
35641    /// Public static version for use by other modules
35642    pub fn strftime_to_java_format_static(fmt: &str) -> String {
35643        Self::strftime_to_java_format(fmt)
35644    }
35645
35646    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
35647    fn strftime_to_java_format(fmt: &str) -> String {
35648        let mut result = String::with_capacity(fmt.len() * 2);
35649        let bytes = fmt.as_bytes();
35650        let len = bytes.len();
35651        let mut i = 0;
35652        while i < len {
35653            if bytes[i] == b'%' && i + 1 < len {
35654                // Check for non-padded variants (%-X)
35655                if bytes[i + 1] == b'-' && i + 2 < len {
35656                    let replacement = match bytes[i + 2] {
35657                        b'd' => "d",
35658                        b'm' => "M",
35659                        b'H' => "H",
35660                        b'M' => "m",
35661                        b'S' => "s",
35662                        _ => {
35663                            result.push('%');
35664                            i += 1;
35665                            continue;
35666                        }
35667                    };
35668                    result.push_str(replacement);
35669                    i += 3;
35670                } else {
35671                    let replacement = match bytes[i + 1] {
35672                        b'Y' => "yyyy",
35673                        b'y' => "yy",
35674                        b'm' => "MM",
35675                        b'B' => "MMMM",
35676                        b'b' => "MMM",
35677                        b'd' => "dd",
35678                        b'j' => "DDD",
35679                        b'H' => "HH",
35680                        b'M' => "mm",
35681                        b'S' => "ss",
35682                        b'f' => "SSSSSS",
35683                        b'A' => "EEEE",
35684                        b'a' => "EEE",
35685                        _ => {
35686                            result.push('%');
35687                            i += 1;
35688                            continue;
35689                        }
35690                    };
35691                    result.push_str(replacement);
35692                    i += 2;
35693                }
35694            } else {
35695                result.push(bytes[i] as char);
35696                i += 1;
35697            }
35698        }
35699        result
35700    }
35701
35702    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
35703    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
35704    fn strftime_to_tsql_format(fmt: &str) -> String {
35705        let mut result = String::with_capacity(fmt.len() * 2);
35706        let bytes = fmt.as_bytes();
35707        let len = bytes.len();
35708        let mut i = 0;
35709        while i < len {
35710            if bytes[i] == b'%' && i + 1 < len {
35711                // Check for non-padded variants (%-X)
35712                if bytes[i + 1] == b'-' && i + 2 < len {
35713                    let replacement = match bytes[i + 2] {
35714                        b'd' => "d",
35715                        b'm' => "M",
35716                        b'H' => "H",
35717                        b'M' => "m",
35718                        b'S' => "s",
35719                        _ => {
35720                            result.push('%');
35721                            i += 1;
35722                            continue;
35723                        }
35724                    };
35725                    result.push_str(replacement);
35726                    i += 3;
35727                } else {
35728                    let replacement = match bytes[i + 1] {
35729                        b'Y' => "yyyy",
35730                        b'y' => "yy",
35731                        b'm' => "MM",
35732                        b'B' => "MMMM",
35733                        b'b' => "MMM",
35734                        b'd' => "dd",
35735                        b'j' => "DDD",
35736                        b'H' => "HH",
35737                        b'M' => "mm",
35738                        b'S' => "ss",
35739                        b'f' => "ffffff",
35740                        b'A' => "dddd",
35741                        b'a' => "ddd",
35742                        _ => {
35743                            result.push('%');
35744                            i += 1;
35745                            continue;
35746                        }
35747                    };
35748                    result.push_str(replacement);
35749                    i += 2;
35750                }
35751            } else {
35752                result.push(bytes[i] as char);
35753                i += 1;
35754            }
35755        }
35756        result
35757    }
35758
35759    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
35760    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
35761    fn decompose_json_path(path: &str) -> Vec<String> {
35762        let mut parts = Vec::new();
35763        // Strip leading $ and optional .
35764        let path = if path.starts_with("$.") {
35765            &path[2..]
35766        } else if path.starts_with('$') {
35767            &path[1..]
35768        } else {
35769            path
35770        };
35771        if path.is_empty() {
35772            return parts;
35773        }
35774        let mut current = String::new();
35775        let chars: Vec<char> = path.chars().collect();
35776        let mut i = 0;
35777        while i < chars.len() {
35778            match chars[i] {
35779                '.' => {
35780                    if !current.is_empty() {
35781                        parts.push(current.clone());
35782                        current.clear();
35783                    }
35784                    i += 1;
35785                }
35786                '[' => {
35787                    if !current.is_empty() {
35788                        parts.push(current.clone());
35789                        current.clear();
35790                    }
35791                    i += 1;
35792                    // Read the content inside brackets
35793                    let mut bracket_content = String::new();
35794                    while i < chars.len() && chars[i] != ']' {
35795                        // Skip quotes inside brackets
35796                        if chars[i] == '"' || chars[i] == '\'' {
35797                            let quote = chars[i];
35798                            i += 1;
35799                            while i < chars.len() && chars[i] != quote {
35800                                bracket_content.push(chars[i]);
35801                                i += 1;
35802                            }
35803                            if i < chars.len() {
35804                                i += 1;
35805                            } // skip closing quote
35806                        } else {
35807                            bracket_content.push(chars[i]);
35808                            i += 1;
35809                        }
35810                    }
35811                    if i < chars.len() {
35812                        i += 1;
35813                    } // skip ]
35814                      // Skip wildcard [*] - don't add as a part
35815                    if bracket_content != "*" {
35816                        parts.push(bracket_content);
35817                    }
35818                }
35819                _ => {
35820                    current.push(chars[i]);
35821                    i += 1;
35822                }
35823            }
35824        }
35825        if !current.is_empty() {
35826            parts.push(current);
35827        }
35828        parts
35829    }
35830
35831    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
35832    fn strftime_to_postgres_format(fmt: &str) -> String {
35833        let mut result = String::with_capacity(fmt.len() * 2);
35834        let bytes = fmt.as_bytes();
35835        let len = bytes.len();
35836        let mut i = 0;
35837        while i < len {
35838            if bytes[i] == b'%' && i + 1 < len {
35839                // Check for non-padded variants (%-X)
35840                if bytes[i + 1] == b'-' && i + 2 < len {
35841                    let replacement = match bytes[i + 2] {
35842                        b'd' => "FMDD",
35843                        b'm' => "FMMM",
35844                        b'H' => "FMHH24",
35845                        b'M' => "FMMI",
35846                        b'S' => "FMSS",
35847                        _ => {
35848                            result.push('%');
35849                            i += 1;
35850                            continue;
35851                        }
35852                    };
35853                    result.push_str(replacement);
35854                    i += 3;
35855                } else {
35856                    let replacement = match bytes[i + 1] {
35857                        b'Y' => "YYYY",
35858                        b'y' => "YY",
35859                        b'm' => "MM",
35860                        b'B' => "Month",
35861                        b'b' => "Mon",
35862                        b'd' => "DD",
35863                        b'j' => "DDD",
35864                        b'H' => "HH24",
35865                        b'M' => "MI",
35866                        b'S' => "SS",
35867                        b'f' => "US",
35868                        b'A' => "Day",
35869                        b'a' => "Dy",
35870                        _ => {
35871                            result.push('%');
35872                            i += 1;
35873                            continue;
35874                        }
35875                    };
35876                    result.push_str(replacement);
35877                    i += 2;
35878                }
35879            } else {
35880                result.push(bytes[i] as char);
35881                i += 1;
35882            }
35883        }
35884        result
35885    }
35886
35887    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
35888    fn strftime_to_snowflake_format(fmt: &str) -> String {
35889        let mut result = String::with_capacity(fmt.len() * 2);
35890        let bytes = fmt.as_bytes();
35891        let len = bytes.len();
35892        let mut i = 0;
35893        while i < len {
35894            if bytes[i] == b'%' && i + 1 < len {
35895                // Check for non-padded variants (%-X)
35896                if bytes[i + 1] == b'-' && i + 2 < len {
35897                    let replacement = match bytes[i + 2] {
35898                        b'd' => "dd",
35899                        b'm' => "mm",
35900                        _ => {
35901                            result.push('%');
35902                            i += 1;
35903                            continue;
35904                        }
35905                    };
35906                    result.push_str(replacement);
35907                    i += 3;
35908                } else {
35909                    let replacement = match bytes[i + 1] {
35910                        b'Y' => "yyyy",
35911                        b'y' => "yy",
35912                        b'm' => "mm",
35913                        b'd' => "DD",
35914                        b'H' => "hh24",
35915                        b'M' => "mi",
35916                        b'S' => "ss",
35917                        b'f' => "ff",
35918                        _ => {
35919                            result.push('%');
35920                            i += 1;
35921                            continue;
35922                        }
35923                    };
35924                    result.push_str(replacement);
35925                    i += 2;
35926                }
35927            } else {
35928                result.push(bytes[i] as char);
35929                i += 1;
35930            }
35931        }
35932        result
35933    }
35934
35935    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
35936        // STR_TO_MAP(this, pair_delim, key_value_delim)
35937        self.write_keyword("STR_TO_MAP");
35938        self.write("(");
35939        self.generate_expression(&e.this)?;
35940        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
35941        let needs_defaults = matches!(
35942            self.config.dialect,
35943            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
35944        );
35945        if let Some(pair_delim) = &e.pair_delim {
35946            self.write(", ");
35947            self.generate_expression(pair_delim)?;
35948        } else if needs_defaults {
35949            self.write(", ','");
35950        }
35951        if let Some(key_value_delim) = &e.key_value_delim {
35952            self.write(", ");
35953            self.generate_expression(key_value_delim)?;
35954        } else if needs_defaults {
35955            self.write(", ':'");
35956        }
35957        self.write(")");
35958        Ok(())
35959    }
35960
35961    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
35962        // Detect format style: strftime (starts with %) vs Snowflake/Java
35963        let is_strftime = e.format.contains('%');
35964        // Helper: get strftime format from whatever style is stored
35965        let to_strftime = |f: &str| -> String {
35966            if is_strftime {
35967                f.to_string()
35968            } else {
35969                Self::snowflake_format_to_strftime(f)
35970            }
35971        };
35972        // Helper: get Java format
35973        let to_java = |f: &str| -> String {
35974            if is_strftime {
35975                Self::strftime_to_java_format(f)
35976            } else {
35977                Self::snowflake_format_to_spark(f)
35978            }
35979        };
35980        // Helper: get PG format
35981        let to_pg = |f: &str| -> String {
35982            if is_strftime {
35983                Self::strftime_to_postgres_format(f)
35984            } else {
35985                Self::convert_strptime_to_postgres_format(f)
35986            }
35987        };
35988
35989        match self.config.dialect {
35990            Some(DialectType::Exasol) => {
35991                self.write_keyword("TO_DATE");
35992                self.write("(");
35993                self.generate_expression(&e.this)?;
35994                self.write(", '");
35995                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
35996                self.write("'");
35997                self.write(")");
35998            }
35999            Some(DialectType::BigQuery) => {
36000                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
36001                let fmt = to_strftime(&e.format);
36002                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
36003                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
36004                self.write_keyword("PARSE_TIMESTAMP");
36005                self.write("('");
36006                self.write(&fmt);
36007                self.write("', ");
36008                self.generate_expression(&e.this)?;
36009                self.write(")");
36010            }
36011            Some(DialectType::Hive) => {
36012                // Hive: CAST(x AS TIMESTAMP) for simple date formats
36013                // Check both the raw format and the converted format (in case it's already Java)
36014                let java_fmt = to_java(&e.format);
36015                if java_fmt == "yyyy-MM-dd HH:mm:ss"
36016                    || java_fmt == "yyyy-MM-dd"
36017                    || e.format == "yyyy-MM-dd HH:mm:ss"
36018                    || e.format == "yyyy-MM-dd"
36019                {
36020                    self.write_keyword("CAST");
36021                    self.write("(");
36022                    self.generate_expression(&e.this)?;
36023                    self.write(" ");
36024                    self.write_keyword("AS TIMESTAMP");
36025                    self.write(")");
36026                } else {
36027                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
36028                    self.write_keyword("CAST");
36029                    self.write("(");
36030                    self.write_keyword("FROM_UNIXTIME");
36031                    self.write("(");
36032                    self.write_keyword("UNIX_TIMESTAMP");
36033                    self.write("(");
36034                    self.generate_expression(&e.this)?;
36035                    self.write(", '");
36036                    self.write(&java_fmt);
36037                    self.write("')");
36038                    self.write(") ");
36039                    self.write_keyword("AS TIMESTAMP");
36040                    self.write(")");
36041                }
36042            }
36043            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36044                // Spark: TO_TIMESTAMP(value, java_format)
36045                let java_fmt = to_java(&e.format);
36046                self.write_keyword("TO_TIMESTAMP");
36047                self.write("(");
36048                self.generate_expression(&e.this)?;
36049                self.write(", '");
36050                self.write(&java_fmt);
36051                self.write("')");
36052            }
36053            Some(DialectType::MySQL) => {
36054                // MySQL: STR_TO_DATE(value, format)
36055                let mut fmt = to_strftime(&e.format);
36056                // MySQL uses %e for non-padded day, %T for %H:%M:%S
36057                fmt = fmt.replace("%-d", "%e");
36058                fmt = fmt.replace("%-m", "%c");
36059                fmt = fmt.replace("%H:%M:%S", "%T");
36060                self.write_keyword("STR_TO_DATE");
36061                self.write("(");
36062                self.generate_expression(&e.this)?;
36063                self.write(", '");
36064                self.write(&fmt);
36065                self.write("')");
36066            }
36067            Some(DialectType::Drill) => {
36068                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
36069                let java_fmt = to_java(&e.format);
36070                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
36071                let java_fmt = java_fmt.replace('T', "''T''");
36072                self.write_keyword("TO_TIMESTAMP");
36073                self.write("(");
36074                self.generate_expression(&e.this)?;
36075                self.write(", '");
36076                self.write(&java_fmt);
36077                self.write("')");
36078            }
36079            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
36080                // Presto: DATE_PARSE(value, strftime_format)
36081                let mut fmt = to_strftime(&e.format);
36082                // Presto uses %e for non-padded day, %T for %H:%M:%S
36083                fmt = fmt.replace("%-d", "%e");
36084                fmt = fmt.replace("%-m", "%c");
36085                fmt = fmt.replace("%H:%M:%S", "%T");
36086                self.write_keyword("DATE_PARSE");
36087                self.write("(");
36088                self.generate_expression(&e.this)?;
36089                self.write(", '");
36090                self.write(&fmt);
36091                self.write("')");
36092            }
36093            Some(DialectType::DuckDB) => {
36094                // DuckDB: STRPTIME(value, strftime_format)
36095                let fmt = to_strftime(&e.format);
36096                self.write_keyword("STRPTIME");
36097                self.write("(");
36098                self.generate_expression(&e.this)?;
36099                self.write(", '");
36100                self.write(&fmt);
36101                self.write("')");
36102            }
36103            Some(DialectType::PostgreSQL)
36104            | Some(DialectType::Redshift)
36105            | Some(DialectType::Materialize) => {
36106                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
36107                let pg_fmt = to_pg(&e.format);
36108                self.write_keyword("TO_TIMESTAMP");
36109                self.write("(");
36110                self.generate_expression(&e.this)?;
36111                self.write(", '");
36112                self.write(&pg_fmt);
36113                self.write("')");
36114            }
36115            Some(DialectType::Oracle) => {
36116                // Oracle: TO_TIMESTAMP(value, pg_format)
36117                let pg_fmt = to_pg(&e.format);
36118                self.write_keyword("TO_TIMESTAMP");
36119                self.write("(");
36120                self.generate_expression(&e.this)?;
36121                self.write(", '");
36122                self.write(&pg_fmt);
36123                self.write("')");
36124            }
36125            Some(DialectType::Snowflake) => {
36126                // Snowflake: TO_TIMESTAMP(value, format) - native format
36127                self.write_keyword("TO_TIMESTAMP");
36128                self.write("(");
36129                self.generate_expression(&e.this)?;
36130                self.write(", '");
36131                self.write(&e.format);
36132                self.write("')");
36133            }
36134            _ => {
36135                // Default: STR_TO_TIME(this, format)
36136                self.write_keyword("STR_TO_TIME");
36137                self.write("(");
36138                self.generate_expression(&e.this)?;
36139                self.write(", '");
36140                self.write(&e.format);
36141                self.write("'");
36142                self.write(")");
36143            }
36144        }
36145        Ok(())
36146    }
36147
36148    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
36149    fn snowflake_format_to_strftime(format: &str) -> String {
36150        let mut result = String::new();
36151        let chars: Vec<char> = format.chars().collect();
36152        let mut i = 0;
36153        while i < chars.len() {
36154            let remaining = &format[i..];
36155            if remaining.starts_with("yyyy") {
36156                result.push_str("%Y");
36157                i += 4;
36158            } else if remaining.starts_with("yy") {
36159                result.push_str("%y");
36160                i += 2;
36161            } else if remaining.starts_with("mmmm") {
36162                result.push_str("%B"); // full month name
36163                i += 4;
36164            } else if remaining.starts_with("mon") {
36165                result.push_str("%b"); // abbreviated month
36166                i += 3;
36167            } else if remaining.starts_with("mm") {
36168                result.push_str("%m");
36169                i += 2;
36170            } else if remaining.starts_with("DD") {
36171                result.push_str("%d");
36172                i += 2;
36173            } else if remaining.starts_with("dy") {
36174                result.push_str("%a"); // abbreviated day name
36175                i += 2;
36176            } else if remaining.starts_with("hh24") {
36177                result.push_str("%H");
36178                i += 4;
36179            } else if remaining.starts_with("hh12") {
36180                result.push_str("%I");
36181                i += 4;
36182            } else if remaining.starts_with("hh") {
36183                result.push_str("%H");
36184                i += 2;
36185            } else if remaining.starts_with("mi") {
36186                result.push_str("%M");
36187                i += 2;
36188            } else if remaining.starts_with("ss") {
36189                result.push_str("%S");
36190                i += 2;
36191            } else if remaining.starts_with("ff") {
36192                // Fractional seconds
36193                result.push_str("%f");
36194                i += 2;
36195                // Skip digits after ff (ff3, ff6, ff9)
36196                while i < chars.len() && chars[i].is_ascii_digit() {
36197                    i += 1;
36198                }
36199            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
36200                result.push_str("%p");
36201                i += 2;
36202            } else if remaining.starts_with("tz") {
36203                result.push_str("%Z");
36204                i += 2;
36205            } else {
36206                result.push(chars[i]);
36207                i += 1;
36208            }
36209        }
36210        result
36211    }
36212
36213    /// Convert Snowflake normalized format to Spark format (Java-style)
36214    fn snowflake_format_to_spark(format: &str) -> String {
36215        let mut result = String::new();
36216        let chars: Vec<char> = format.chars().collect();
36217        let mut i = 0;
36218        while i < chars.len() {
36219            let remaining = &format[i..];
36220            if remaining.starts_with("yyyy") {
36221                result.push_str("yyyy");
36222                i += 4;
36223            } else if remaining.starts_with("yy") {
36224                result.push_str("yy");
36225                i += 2;
36226            } else if remaining.starts_with("mmmm") {
36227                result.push_str("MMMM"); // full month name
36228                i += 4;
36229            } else if remaining.starts_with("mon") {
36230                result.push_str("MMM"); // abbreviated month
36231                i += 3;
36232            } else if remaining.starts_with("mm") {
36233                result.push_str("MM");
36234                i += 2;
36235            } else if remaining.starts_with("DD") {
36236                result.push_str("dd");
36237                i += 2;
36238            } else if remaining.starts_with("dy") {
36239                result.push_str("EEE"); // abbreviated day name
36240                i += 2;
36241            } else if remaining.starts_with("hh24") {
36242                result.push_str("HH");
36243                i += 4;
36244            } else if remaining.starts_with("hh12") {
36245                result.push_str("hh");
36246                i += 4;
36247            } else if remaining.starts_with("hh") {
36248                result.push_str("HH");
36249                i += 2;
36250            } else if remaining.starts_with("mi") {
36251                result.push_str("mm");
36252                i += 2;
36253            } else if remaining.starts_with("ss") {
36254                result.push_str("ss");
36255                i += 2;
36256            } else if remaining.starts_with("ff") {
36257                result.push_str("SSS"); // milliseconds
36258                i += 2;
36259                // Skip digits after ff
36260                while i < chars.len() && chars[i].is_ascii_digit() {
36261                    i += 1;
36262                }
36263            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
36264                result.push_str("a");
36265                i += 2;
36266            } else if remaining.starts_with("tz") {
36267                result.push_str("z");
36268                i += 2;
36269            } else {
36270                result.push(chars[i]);
36271                i += 1;
36272            }
36273        }
36274        result
36275    }
36276
36277    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
36278        match self.config.dialect {
36279            Some(DialectType::DuckDB) => {
36280                // DuckDB: EPOCH(STRPTIME(value, format))
36281                self.write_keyword("EPOCH");
36282                self.write("(");
36283                self.write_keyword("STRPTIME");
36284                self.write("(");
36285                if let Some(this) = &e.this {
36286                    self.generate_expression(this)?;
36287                }
36288                if let Some(format) = &e.format {
36289                    self.write(", '");
36290                    self.write(format);
36291                    self.write("'");
36292                }
36293                self.write("))");
36294            }
36295            Some(DialectType::Hive) => {
36296                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
36297                self.write_keyword("UNIX_TIMESTAMP");
36298                self.write("(");
36299                if let Some(this) = &e.this {
36300                    self.generate_expression(this)?;
36301                }
36302                if let Some(format) = &e.format {
36303                    let java_fmt = Self::strftime_to_java_format(format);
36304                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
36305                        self.write(", '");
36306                        self.write(&java_fmt);
36307                        self.write("'");
36308                    }
36309                }
36310                self.write(")");
36311            }
36312            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36313                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
36314                self.write_keyword("UNIX_TIMESTAMP");
36315                self.write("(");
36316                if let Some(this) = &e.this {
36317                    self.generate_expression(this)?;
36318                }
36319                if let Some(format) = &e.format {
36320                    self.write(", '");
36321                    self.write(format);
36322                    self.write("'");
36323                }
36324                self.write(")");
36325            }
36326            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36327                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
36328                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
36329                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
36330                let java_fmt = Self::strftime_to_java_format(c_fmt);
36331                self.write_keyword("TO_UNIXTIME");
36332                self.write("(");
36333                self.write_keyword("COALESCE");
36334                self.write("(");
36335                self.write_keyword("TRY");
36336                self.write("(");
36337                self.write_keyword("DATE_PARSE");
36338                self.write("(");
36339                self.write_keyword("CAST");
36340                self.write("(");
36341                if let Some(this) = &e.this {
36342                    self.generate_expression(this)?;
36343                }
36344                self.write(" ");
36345                self.write_keyword("AS VARCHAR");
36346                self.write("), '");
36347                self.write(c_fmt);
36348                self.write("')), ");
36349                self.write_keyword("PARSE_DATETIME");
36350                self.write("(");
36351                self.write_keyword("DATE_FORMAT");
36352                self.write("(");
36353                self.write_keyword("CAST");
36354                self.write("(");
36355                if let Some(this) = &e.this {
36356                    self.generate_expression(this)?;
36357                }
36358                self.write(" ");
36359                self.write_keyword("AS TIMESTAMP");
36360                self.write("), '");
36361                self.write(c_fmt);
36362                self.write("'), '");
36363                self.write(&java_fmt);
36364                self.write("')))");
36365            }
36366            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36367                // Spark: UNIX_TIMESTAMP(value, java_format)
36368                self.write_keyword("UNIX_TIMESTAMP");
36369                self.write("(");
36370                if let Some(this) = &e.this {
36371                    self.generate_expression(this)?;
36372                }
36373                if let Some(format) = &e.format {
36374                    let java_fmt = Self::strftime_to_java_format(format);
36375                    self.write(", '");
36376                    self.write(&java_fmt);
36377                    self.write("'");
36378                }
36379                self.write(")");
36380            }
36381            _ => {
36382                // Default: STR_TO_UNIX(this, format)
36383                self.write_keyword("STR_TO_UNIX");
36384                self.write("(");
36385                if let Some(this) = &e.this {
36386                    self.generate_expression(this)?;
36387                }
36388                if let Some(format) = &e.format {
36389                    self.write(", '");
36390                    self.write(format);
36391                    self.write("'");
36392                }
36393                self.write(")");
36394            }
36395        }
36396        Ok(())
36397    }
36398
36399    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
36400        // STRING_TO_ARRAY(this, delimiter, null_string)
36401        self.write_keyword("STRING_TO_ARRAY");
36402        self.write("(");
36403        self.generate_expression(&e.this)?;
36404        if let Some(expression) = &e.expression {
36405            self.write(", ");
36406            self.generate_expression(expression)?;
36407        }
36408        if let Some(null_val) = &e.null {
36409            self.write(", ");
36410            self.generate_expression(null_val)?;
36411        }
36412        self.write(")");
36413        Ok(())
36414    }
36415
36416    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
36417        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36418            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
36419            self.write_keyword("OBJECT_CONSTRUCT");
36420            self.write("(");
36421            for (i, (name, expr)) in e.fields.iter().enumerate() {
36422                if i > 0 {
36423                    self.write(", ");
36424                }
36425                if let Some(name) = name {
36426                    self.write("'");
36427                    self.write(name);
36428                    self.write("'");
36429                    self.write(", ");
36430                } else {
36431                    self.write("'_");
36432                    self.write(&i.to_string());
36433                    self.write("'");
36434                    self.write(", ");
36435                }
36436                self.generate_expression(expr)?;
36437            }
36438            self.write(")");
36439        } else if self.config.struct_curly_brace_notation {
36440            // DuckDB-style: {'key': value, ...}
36441            self.write("{");
36442            for (i, (name, expr)) in e.fields.iter().enumerate() {
36443                if i > 0 {
36444                    self.write(", ");
36445                }
36446                if let Some(name) = name {
36447                    // Quote the key as a string literal
36448                    self.write("'");
36449                    self.write(name);
36450                    self.write("'");
36451                    self.write(": ");
36452                } else {
36453                    // Unnamed field: use positional key
36454                    self.write("'_");
36455                    self.write(&i.to_string());
36456                    self.write("'");
36457                    self.write(": ");
36458                }
36459                self.generate_expression(expr)?;
36460            }
36461            self.write("}");
36462        } else {
36463            // Standard SQL struct notation
36464            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
36465            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
36466            let value_as_name = matches!(
36467                self.config.dialect,
36468                Some(DialectType::BigQuery)
36469                    | Some(DialectType::Spark)
36470                    | Some(DialectType::Databricks)
36471                    | Some(DialectType::Hive)
36472            );
36473            self.write_keyword("STRUCT");
36474            self.write("(");
36475            for (i, (name, expr)) in e.fields.iter().enumerate() {
36476                if i > 0 {
36477                    self.write(", ");
36478                }
36479                if let Some(name) = name {
36480                    if value_as_name {
36481                        // STRUCT(value AS name)
36482                        self.generate_expression(expr)?;
36483                        self.write_space();
36484                        self.write_keyword("AS");
36485                        self.write_space();
36486                        // Quote name if it contains spaces or special chars
36487                        let needs_quoting = name.contains(' ') || name.contains('-');
36488                        if needs_quoting {
36489                            if matches!(
36490                                self.config.dialect,
36491                                Some(DialectType::Spark)
36492                                    | Some(DialectType::Databricks)
36493                                    | Some(DialectType::Hive)
36494                            ) {
36495                                self.write("`");
36496                                self.write(name);
36497                                self.write("`");
36498                            } else {
36499                                self.write(name);
36500                            }
36501                        } else {
36502                            self.write(name);
36503                        }
36504                    } else {
36505                        // STRUCT(name AS value)
36506                        self.write(name);
36507                        self.write_space();
36508                        self.write_keyword("AS");
36509                        self.write_space();
36510                        self.generate_expression(expr)?;
36511                    }
36512                } else {
36513                    self.generate_expression(expr)?;
36514                }
36515            }
36516            self.write(")");
36517        }
36518        Ok(())
36519    }
36520
36521    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
36522        // STUFF(this, start, length, expression)
36523        self.write_keyword("STUFF");
36524        self.write("(");
36525        self.generate_expression(&e.this)?;
36526        if let Some(start) = &e.start {
36527            self.write(", ");
36528            self.generate_expression(start)?;
36529        }
36530        if let Some(length) = e.length {
36531            self.write(", ");
36532            self.write(&length.to_string());
36533        }
36534        self.write(", ");
36535        self.generate_expression(&e.expression)?;
36536        self.write(")");
36537        Ok(())
36538    }
36539
36540    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
36541        // SUBSTRING_INDEX(this, delimiter, count)
36542        self.write_keyword("SUBSTRING_INDEX");
36543        self.write("(");
36544        self.generate_expression(&e.this)?;
36545        if let Some(delimiter) = &e.delimiter {
36546            self.write(", ");
36547            self.generate_expression(delimiter)?;
36548        }
36549        if let Some(count) = &e.count {
36550            self.write(", ");
36551            self.generate_expression(count)?;
36552        }
36553        self.write(")");
36554        Ok(())
36555    }
36556
36557    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
36558        // SUMMARIZE [TABLE] this
36559        self.write_keyword("SUMMARIZE");
36560        if e.table.is_some() {
36561            self.write_space();
36562            self.write_keyword("TABLE");
36563        }
36564        self.write_space();
36565        self.generate_expression(&e.this)?;
36566        Ok(())
36567    }
36568
36569    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
36570        // SYSTIMESTAMP
36571        self.write_keyword("SYSTIMESTAMP");
36572        Ok(())
36573    }
36574
36575    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
36576        // alias (columns...)
36577        if let Some(this) = &e.this {
36578            self.generate_expression(this)?;
36579        }
36580        if !e.columns.is_empty() {
36581            self.write("(");
36582            for (i, col) in e.columns.iter().enumerate() {
36583                if i > 0 {
36584                    self.write(", ");
36585                }
36586                self.generate_expression(col)?;
36587            }
36588            self.write(")");
36589        }
36590        Ok(())
36591    }
36592
36593    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
36594        // TABLE(this) [AS alias]
36595        self.write_keyword("TABLE");
36596        self.write("(");
36597        self.generate_expression(&e.this)?;
36598        self.write(")");
36599        if let Some(alias) = &e.alias {
36600            self.write_space();
36601            self.write_keyword("AS");
36602            self.write_space();
36603            self.write(alias);
36604        }
36605        Ok(())
36606    }
36607
36608    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
36609        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
36610        self.write_keyword("ROWS FROM");
36611        self.write(" (");
36612        for (i, expr) in e.expressions.iter().enumerate() {
36613            if i > 0 {
36614                self.write(", ");
36615            }
36616            // Each expression is either:
36617            // - A plain function (no alias)
36618            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
36619            match expr {
36620                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
36621                    // First element is the function, second is the TableAlias
36622                    self.generate_expression(&tuple.expressions[0])?;
36623                    self.write_space();
36624                    self.write_keyword("AS");
36625                    self.write_space();
36626                    self.generate_expression(&tuple.expressions[1])?;
36627                }
36628                _ => {
36629                    self.generate_expression(expr)?;
36630                }
36631            }
36632        }
36633        self.write(")");
36634        if e.ordinality {
36635            self.write_space();
36636            self.write_keyword("WITH ORDINALITY");
36637        }
36638        if let Some(alias) = &e.alias {
36639            self.write_space();
36640            self.write_keyword("AS");
36641            self.write_space();
36642            self.generate_expression(alias)?;
36643        }
36644        Ok(())
36645    }
36646
36647    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
36648        use crate::dialects::DialectType;
36649
36650        // New wrapper pattern: expression + Sample struct
36651        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
36652            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
36653            if self.config.alias_post_tablesample {
36654                // Handle Subquery with alias and Alias wrapper
36655                if let Expression::Subquery(ref s) = **this {
36656                    if let Some(ref alias) = s.alias {
36657                        // Create a clone without alias for output
36658                        let mut subquery_no_alias = (**s).clone();
36659                        subquery_no_alias.alias = None;
36660                        subquery_no_alias.column_aliases = Vec::new();
36661                        self.generate_expression(&Expression::Subquery(Box::new(
36662                            subquery_no_alias,
36663                        )))?;
36664                        self.write_space();
36665                        self.write_keyword(self.config.tablesample_keywords);
36666                        self.generate_sample_body(sample)?;
36667                        if let Some(ref seed) = sample.seed {
36668                            self.write_space();
36669                            let use_seed = sample.use_seed_keyword
36670                                && !matches!(
36671                                    self.config.dialect,
36672                                    Some(crate::dialects::DialectType::Databricks)
36673                                        | Some(crate::dialects::DialectType::Spark)
36674                                );
36675                            if use_seed {
36676                                self.write_keyword("SEED");
36677                            } else {
36678                                self.write_keyword("REPEATABLE");
36679                            }
36680                            self.write(" (");
36681                            self.generate_expression(seed)?;
36682                            self.write(")");
36683                        }
36684                        self.write_space();
36685                        self.write_keyword("AS");
36686                        self.write_space();
36687                        self.generate_identifier(alias)?;
36688                        return Ok(());
36689                    }
36690                } else if let Expression::Alias(ref a) = **this {
36691                    // Output the base expression without alias
36692                    self.generate_expression(&a.this)?;
36693                    self.write_space();
36694                    self.write_keyword(self.config.tablesample_keywords);
36695                    self.generate_sample_body(sample)?;
36696                    if let Some(ref seed) = sample.seed {
36697                        self.write_space();
36698                        let use_seed = sample.use_seed_keyword
36699                            && !matches!(
36700                                self.config.dialect,
36701                                Some(crate::dialects::DialectType::Databricks)
36702                                    | Some(crate::dialects::DialectType::Spark)
36703                            );
36704                        if use_seed {
36705                            self.write_keyword("SEED");
36706                        } else {
36707                            self.write_keyword("REPEATABLE");
36708                        }
36709                        self.write(" (");
36710                        self.generate_expression(seed)?;
36711                        self.write(")");
36712                    }
36713                    // Output alias after TABLESAMPLE
36714                    self.write_space();
36715                    self.write_keyword("AS");
36716                    self.write_space();
36717                    self.generate_identifier(&a.alias)?;
36718                    return Ok(());
36719                }
36720            }
36721            // Default: generate wrapped expression first, then TABLESAMPLE
36722            self.generate_expression(this)?;
36723            self.write_space();
36724            self.write_keyword(self.config.tablesample_keywords);
36725            self.generate_sample_body(sample)?;
36726            // Seed for table-level sample
36727            if let Some(ref seed) = sample.seed {
36728                self.write_space();
36729                // Databricks uses REPEATABLE, not SEED
36730                let use_seed = sample.use_seed_keyword
36731                    && !matches!(
36732                        self.config.dialect,
36733                        Some(crate::dialects::DialectType::Databricks)
36734                            | Some(crate::dialects::DialectType::Spark)
36735                    );
36736                if use_seed {
36737                    self.write_keyword("SEED");
36738                } else {
36739                    self.write_keyword("REPEATABLE");
36740                }
36741                self.write(" (");
36742                self.generate_expression(seed)?;
36743                self.write(")");
36744            }
36745            return Ok(());
36746        }
36747
36748        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
36749        self.write_keyword(self.config.tablesample_keywords);
36750        if let Some(method) = &e.method {
36751            self.write_space();
36752            self.write_keyword(method);
36753        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36754            // Snowflake defaults to BERNOULLI when no method is specified
36755            self.write_space();
36756            self.write_keyword("BERNOULLI");
36757        }
36758        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
36759            self.write_space();
36760            self.write_keyword("BUCKET");
36761            self.write_space();
36762            self.generate_expression(numerator)?;
36763            self.write_space();
36764            self.write_keyword("OUT OF");
36765            self.write_space();
36766            self.generate_expression(denominator)?;
36767            if let Some(field) = &e.bucket_field {
36768                self.write_space();
36769                self.write_keyword("ON");
36770                self.write_space();
36771                self.generate_expression(field)?;
36772            }
36773        } else if !e.expressions.is_empty() {
36774            self.write(" (");
36775            for (i, expr) in e.expressions.iter().enumerate() {
36776                if i > 0 {
36777                    self.write(", ");
36778                }
36779                self.generate_expression(expr)?;
36780            }
36781            self.write(")");
36782        } else if let Some(percent) = &e.percent {
36783            self.write(" (");
36784            self.generate_expression(percent)?;
36785            self.write_space();
36786            self.write_keyword("PERCENT");
36787            self.write(")");
36788        }
36789        Ok(())
36790    }
36791
36792    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
36793        // [prefix]this[postfix]
36794        if let Some(prefix) = &e.prefix {
36795            self.generate_expression(prefix)?;
36796        }
36797        if let Some(this) = &e.this {
36798            self.generate_expression(this)?;
36799        }
36800        if let Some(postfix) = &e.postfix {
36801            self.generate_expression(postfix)?;
36802        }
36803        Ok(())
36804    }
36805
36806    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
36807        // TAG (expressions)
36808        self.write_keyword("TAG");
36809        self.write(" (");
36810        for (i, expr) in e.expressions.iter().enumerate() {
36811            if i > 0 {
36812                self.write(", ");
36813            }
36814            self.generate_expression(expr)?;
36815        }
36816        self.write(")");
36817        Ok(())
36818    }
36819
36820    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
36821        // TEMPORARY or TEMP or [this] TEMPORARY
36822        if let Some(this) = &e.this {
36823            self.generate_expression(this)?;
36824            self.write_space();
36825        }
36826        self.write_keyword("TEMPORARY");
36827        Ok(())
36828    }
36829
36830    /// Generate a Time function expression
36831    /// For most dialects: TIME('value')
36832    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
36833        // Standard: TIME(value)
36834        self.write_keyword("TIME");
36835        self.write("(");
36836        self.generate_expression(&e.this)?;
36837        self.write(")");
36838        Ok(())
36839    }
36840
36841    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
36842        // TIME_ADD(this, expression, unit)
36843        self.write_keyword("TIME_ADD");
36844        self.write("(");
36845        self.generate_expression(&e.this)?;
36846        self.write(", ");
36847        self.generate_expression(&e.expression)?;
36848        if let Some(unit) = &e.unit {
36849            self.write(", ");
36850            self.write_keyword(unit);
36851        }
36852        self.write(")");
36853        Ok(())
36854    }
36855
36856    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
36857        // TIME_DIFF(this, expression, unit)
36858        self.write_keyword("TIME_DIFF");
36859        self.write("(");
36860        self.generate_expression(&e.this)?;
36861        self.write(", ");
36862        self.generate_expression(&e.expression)?;
36863        if let Some(unit) = &e.unit {
36864            self.write(", ");
36865            self.write_keyword(unit);
36866        }
36867        self.write(")");
36868        Ok(())
36869    }
36870
36871    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
36872        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
36873        self.write_keyword("TIME_FROM_PARTS");
36874        self.write("(");
36875        let mut first = true;
36876        if let Some(hour) = &e.hour {
36877            self.generate_expression(hour)?;
36878            first = false;
36879        }
36880        if let Some(minute) = &e.min {
36881            if !first {
36882                self.write(", ");
36883            }
36884            self.generate_expression(minute)?;
36885            first = false;
36886        }
36887        if let Some(second) = &e.sec {
36888            if !first {
36889                self.write(", ");
36890            }
36891            self.generate_expression(second)?;
36892            first = false;
36893        }
36894        if let Some(ns) = &e.nano {
36895            if !first {
36896                self.write(", ");
36897            }
36898            self.generate_expression(ns)?;
36899        }
36900        self.write(")");
36901        Ok(())
36902    }
36903
36904    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
36905        // TIME_SLICE(this, expression, unit)
36906        self.write_keyword("TIME_SLICE");
36907        self.write("(");
36908        self.generate_expression(&e.this)?;
36909        self.write(", ");
36910        self.generate_expression(&e.expression)?;
36911        self.write(", ");
36912        self.write_keyword(&e.unit);
36913        self.write(")");
36914        Ok(())
36915    }
36916
36917    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
36918        // TIME_STR_TO_TIME(this)
36919        self.write_keyword("TIME_STR_TO_TIME");
36920        self.write("(");
36921        self.generate_expression(&e.this)?;
36922        self.write(")");
36923        Ok(())
36924    }
36925
36926    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
36927        // TIME_SUB(this, expression, unit)
36928        self.write_keyword("TIME_SUB");
36929        self.write("(");
36930        self.generate_expression(&e.this)?;
36931        self.write(", ");
36932        self.generate_expression(&e.expression)?;
36933        if let Some(unit) = &e.unit {
36934            self.write(", ");
36935            self.write_keyword(unit);
36936        }
36937        self.write(")");
36938        Ok(())
36939    }
36940
36941    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
36942        match self.config.dialect {
36943            Some(DialectType::Exasol) => {
36944                // Exasol uses TO_CHAR with Exasol-specific format
36945                self.write_keyword("TO_CHAR");
36946                self.write("(");
36947                self.generate_expression(&e.this)?;
36948                self.write(", '");
36949                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
36950                self.write("'");
36951                self.write(")");
36952            }
36953            Some(DialectType::PostgreSQL)
36954            | Some(DialectType::Redshift)
36955            | Some(DialectType::Materialize) => {
36956                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
36957                self.write_keyword("TO_CHAR");
36958                self.write("(");
36959                self.generate_expression(&e.this)?;
36960                self.write(", '");
36961                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36962                self.write("'");
36963                self.write(")");
36964            }
36965            Some(DialectType::Oracle) => {
36966                // Oracle uses TO_CHAR with PG-like format
36967                self.write_keyword("TO_CHAR");
36968                self.write("(");
36969                self.generate_expression(&e.this)?;
36970                self.write(", '");
36971                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36972                self.write("'");
36973                self.write(")");
36974            }
36975            Some(DialectType::Drill) => {
36976                // Drill: TO_CHAR with Java format
36977                self.write_keyword("TO_CHAR");
36978                self.write("(");
36979                self.generate_expression(&e.this)?;
36980                self.write(", '");
36981                self.write(&Self::strftime_to_java_format(&e.format));
36982                self.write("'");
36983                self.write(")");
36984            }
36985            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
36986                // TSQL: FORMAT(value, format) with .NET-style format
36987                self.write_keyword("FORMAT");
36988                self.write("(");
36989                self.generate_expression(&e.this)?;
36990                self.write(", '");
36991                self.write(&Self::strftime_to_tsql_format(&e.format));
36992                self.write("'");
36993                self.write(")");
36994            }
36995            Some(DialectType::DuckDB) => {
36996                // DuckDB: STRFTIME(value, format) - keeps C format
36997                self.write_keyword("STRFTIME");
36998                self.write("(");
36999                self.generate_expression(&e.this)?;
37000                self.write(", '");
37001                self.write(&e.format);
37002                self.write("'");
37003                self.write(")");
37004            }
37005            Some(DialectType::BigQuery) => {
37006                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
37007                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
37008                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
37009                self.write_keyword("FORMAT_DATE");
37010                self.write("('");
37011                self.write(&fmt);
37012                self.write("', ");
37013                self.generate_expression(&e.this)?;
37014                self.write(")");
37015            }
37016            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37017                // Hive/Spark: DATE_FORMAT(value, java_format)
37018                self.write_keyword("DATE_FORMAT");
37019                self.write("(");
37020                self.generate_expression(&e.this)?;
37021                self.write(", '");
37022                self.write(&Self::strftime_to_java_format(&e.format));
37023                self.write("'");
37024                self.write(")");
37025            }
37026            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
37027                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
37028                self.write_keyword("DATE_FORMAT");
37029                self.write("(");
37030                self.generate_expression(&e.this)?;
37031                self.write(", '");
37032                self.write(&e.format);
37033                self.write("'");
37034                self.write(")");
37035            }
37036            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
37037                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
37038                self.write_keyword("DATE_FORMAT");
37039                self.write("(");
37040                self.generate_expression(&e.this)?;
37041                self.write(", '");
37042                self.write(&e.format);
37043                self.write("'");
37044                self.write(")");
37045            }
37046            _ => {
37047                // Default: TIME_TO_STR(this, format)
37048                self.write_keyword("TIME_TO_STR");
37049                self.write("(");
37050                self.generate_expression(&e.this)?;
37051                self.write(", '");
37052                self.write(&e.format);
37053                self.write("'");
37054                self.write(")");
37055            }
37056        }
37057        Ok(())
37058    }
37059
37060    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
37061        match self.config.dialect {
37062            Some(DialectType::DuckDB) => {
37063                // DuckDB: EPOCH(x)
37064                self.write_keyword("EPOCH");
37065                self.write("(");
37066                self.generate_expression(&e.this)?;
37067                self.write(")");
37068            }
37069            Some(DialectType::Hive)
37070            | Some(DialectType::Spark)
37071            | Some(DialectType::Databricks)
37072            | Some(DialectType::Doris)
37073            | Some(DialectType::StarRocks)
37074            | Some(DialectType::Drill) => {
37075                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
37076                self.write_keyword("UNIX_TIMESTAMP");
37077                self.write("(");
37078                self.generate_expression(&e.this)?;
37079                self.write(")");
37080            }
37081            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37082                // Presto: TO_UNIXTIME(x)
37083                self.write_keyword("TO_UNIXTIME");
37084                self.write("(");
37085                self.generate_expression(&e.this)?;
37086                self.write(")");
37087            }
37088            _ => {
37089                // Default: TIME_TO_UNIX(x)
37090                self.write_keyword("TIME_TO_UNIX");
37091                self.write("(");
37092                self.generate_expression(&e.this)?;
37093                self.write(")");
37094            }
37095        }
37096        Ok(())
37097    }
37098
37099    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
37100        match self.config.dialect {
37101            Some(DialectType::Hive) => {
37102                // Hive: TO_DATE(x)
37103                self.write_keyword("TO_DATE");
37104                self.write("(");
37105                self.generate_expression(&e.this)?;
37106                self.write(")");
37107            }
37108            _ => {
37109                // Default: TIME_STR_TO_DATE(x)
37110                self.write_keyword("TIME_STR_TO_DATE");
37111                self.write("(");
37112                self.generate_expression(&e.this)?;
37113                self.write(")");
37114            }
37115        }
37116        Ok(())
37117    }
37118
37119    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
37120        // TIME_TRUNC(this, unit)
37121        self.write_keyword("TIME_TRUNC");
37122        self.write("(");
37123        self.generate_expression(&e.this)?;
37124        self.write(", ");
37125        self.write_keyword(&e.unit);
37126        self.write(")");
37127        Ok(())
37128    }
37129
37130    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
37131        // Just output the unit name
37132        if let Some(unit) = &e.unit {
37133            self.write_keyword(unit);
37134        }
37135        Ok(())
37136    }
37137
37138    /// Generate a Timestamp function expression
37139    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
37140    /// For other dialects: TIMESTAMP('value')
37141    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
37142        use crate::dialects::DialectType;
37143        use crate::expressions::Literal;
37144
37145        match self.config.dialect {
37146            // Exasol uses TO_TIMESTAMP for Timestamp expressions
37147            Some(DialectType::Exasol) => {
37148                self.write_keyword("TO_TIMESTAMP");
37149                self.write("(");
37150                // Extract the string value from the expression if it's a string literal
37151                if let Some(this) = &e.this {
37152                    match this.as_ref() {
37153                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
37154                            let Literal::String(s) = lit.as_ref() else {
37155                                unreachable!()
37156                            };
37157                            self.write("'");
37158                            self.write(s);
37159                            self.write("'");
37160                        }
37161                        _ => {
37162                            self.generate_expression(this)?;
37163                        }
37164                    }
37165                }
37166                self.write(")");
37167            }
37168            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
37169            _ => {
37170                self.write_keyword("TIMESTAMP");
37171                self.write("(");
37172                if let Some(this) = &e.this {
37173                    self.generate_expression(this)?;
37174                }
37175                if let Some(zone) = &e.zone {
37176                    self.write(", ");
37177                    self.generate_expression(zone)?;
37178                }
37179                self.write(")");
37180            }
37181        }
37182        Ok(())
37183    }
37184
37185    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
37186        // TIMESTAMP_ADD(this, expression, unit)
37187        self.write_keyword("TIMESTAMP_ADD");
37188        self.write("(");
37189        self.generate_expression(&e.this)?;
37190        self.write(", ");
37191        self.generate_expression(&e.expression)?;
37192        if let Some(unit) = &e.unit {
37193            self.write(", ");
37194            self.write_keyword(unit);
37195        }
37196        self.write(")");
37197        Ok(())
37198    }
37199
37200    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
37201        // TIMESTAMP_DIFF(this, expression, unit)
37202        self.write_keyword("TIMESTAMP_DIFF");
37203        self.write("(");
37204        self.generate_expression(&e.this)?;
37205        self.write(", ");
37206        self.generate_expression(&e.expression)?;
37207        if let Some(unit) = &e.unit {
37208            self.write(", ");
37209            self.write_keyword(unit);
37210        }
37211        self.write(")");
37212        Ok(())
37213    }
37214
37215    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
37216        // TIMESTAMP_FROM_PARTS(this, expression)
37217        self.write_keyword("TIMESTAMP_FROM_PARTS");
37218        self.write("(");
37219        if let Some(this) = &e.this {
37220            self.generate_expression(this)?;
37221        }
37222        if let Some(expression) = &e.expression {
37223            self.write(", ");
37224            self.generate_expression(expression)?;
37225        }
37226        if let Some(zone) = &e.zone {
37227            self.write(", ");
37228            self.generate_expression(zone)?;
37229        }
37230        if let Some(milli) = &e.milli {
37231            self.write(", ");
37232            self.generate_expression(milli)?;
37233        }
37234        self.write(")");
37235        Ok(())
37236    }
37237
37238    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
37239        // TIMESTAMP_SUB(this, INTERVAL expression unit)
37240        self.write_keyword("TIMESTAMP_SUB");
37241        self.write("(");
37242        self.generate_expression(&e.this)?;
37243        self.write(", ");
37244        self.write_keyword("INTERVAL");
37245        self.write_space();
37246        self.generate_expression(&e.expression)?;
37247        if let Some(unit) = &e.unit {
37248            self.write_space();
37249            self.write_keyword(unit);
37250        }
37251        self.write(")");
37252        Ok(())
37253    }
37254
37255    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
37256        // TIMESTAMP_TZ_FROM_PARTS(...)
37257        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
37258        self.write("(");
37259        if let Some(zone) = &e.zone {
37260            self.generate_expression(zone)?;
37261        }
37262        self.write(")");
37263        Ok(())
37264    }
37265
37266    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
37267        // TO_BINARY(this, [format])
37268        self.write_keyword("TO_BINARY");
37269        self.write("(");
37270        self.generate_expression(&e.this)?;
37271        if let Some(format) = &e.format {
37272            self.write(", '");
37273            self.write(format);
37274            self.write("'");
37275        }
37276        self.write(")");
37277        Ok(())
37278    }
37279
37280    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
37281        // TO_BOOLEAN(this)
37282        self.write_keyword("TO_BOOLEAN");
37283        self.write("(");
37284        self.generate_expression(&e.this)?;
37285        self.write(")");
37286        Ok(())
37287    }
37288
37289    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
37290        // TO_CHAR(this, [format], [nlsparam])
37291        self.write_keyword("TO_CHAR");
37292        self.write("(");
37293        self.generate_expression(&e.this)?;
37294        if let Some(format) = &e.format {
37295            self.write(", '");
37296            self.write(format);
37297            self.write("'");
37298        }
37299        if let Some(nlsparam) = &e.nlsparam {
37300            self.write(", ");
37301            self.generate_expression(nlsparam)?;
37302        }
37303        self.write(")");
37304        Ok(())
37305    }
37306
37307    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
37308        // TO_DECFLOAT(this, [format])
37309        self.write_keyword("TO_DECFLOAT");
37310        self.write("(");
37311        self.generate_expression(&e.this)?;
37312        if let Some(format) = &e.format {
37313            self.write(", '");
37314            self.write(format);
37315            self.write("'");
37316        }
37317        self.write(")");
37318        Ok(())
37319    }
37320
37321    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
37322        // TO_DOUBLE(this, [format])
37323        self.write_keyword("TO_DOUBLE");
37324        self.write("(");
37325        self.generate_expression(&e.this)?;
37326        if let Some(format) = &e.format {
37327            self.write(", '");
37328            self.write(format);
37329            self.write("'");
37330        }
37331        self.write(")");
37332        Ok(())
37333    }
37334
37335    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
37336        // TO_FILE(this, path)
37337        self.write_keyword("TO_FILE");
37338        self.write("(");
37339        self.generate_expression(&e.this)?;
37340        if let Some(path) = &e.path {
37341            self.write(", ");
37342            self.generate_expression(path)?;
37343        }
37344        self.write(")");
37345        Ok(())
37346    }
37347
37348    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
37349        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
37350        // If safe flag is set, output TRY_TO_NUMBER
37351        let is_safe = e.safe.is_some();
37352        if is_safe {
37353            self.write_keyword("TRY_TO_NUMBER");
37354        } else {
37355            self.write_keyword("TO_NUMBER");
37356        }
37357        self.write("(");
37358        self.generate_expression(&e.this)?;
37359        let precision_is_snowflake_default = e.precision.is_none()
37360            || matches!(
37361                e.precision.as_deref(),
37362                Some(Expression::Literal(lit))
37363                    if matches!(lit.as_ref(), Literal::Number(n) if n == "0")
37364            );
37365        let is_snowflake_default_precision =
37366            matches!(self.config.dialect, Some(DialectType::Snowflake))
37367                && e.nlsparam.is_none()
37368                && e.scale.is_none()
37369                && matches!(
37370                    e.format.as_deref(),
37371                    Some(Expression::Literal(lit))
37372                        if matches!(lit.as_ref(), Literal::Number(n) if n == "38")
37373                )
37374                && precision_is_snowflake_default;
37375
37376        if !is_snowflake_default_precision {
37377            if let Some(format) = &e.format {
37378                self.write(", ");
37379                self.generate_expression(format)?;
37380            }
37381            if let Some(nlsparam) = &e.nlsparam {
37382                self.write(", ");
37383                self.generate_expression(nlsparam)?;
37384            }
37385            if let Some(precision) = &e.precision {
37386                self.write(", ");
37387                self.generate_expression(precision)?;
37388            }
37389            if let Some(scale) = &e.scale {
37390                self.write(", ");
37391                self.generate_expression(scale)?;
37392            }
37393        }
37394        self.write(")");
37395        Ok(())
37396    }
37397
37398    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
37399        // TO_TABLE this
37400        self.write_keyword("TO_TABLE");
37401        self.write_space();
37402        self.generate_expression(&e.this)?;
37403        Ok(())
37404    }
37405
37406    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
37407        // Check mark to determine the format
37408        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
37409            Expression::Identifier(id) => id.name.clone(),
37410            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
37411                let Literal::String(s) = lit.as_ref() else {
37412                    unreachable!()
37413                };
37414                s.clone()
37415            }
37416            _ => String::new(),
37417        });
37418
37419        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
37420        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
37421        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
37422            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
37423        });
37424
37425        // For Presto/Trino: always use START TRANSACTION
37426        let use_start_transaction = matches!(
37427            self.config.dialect,
37428            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
37429        );
37430        // For most dialects: strip TRANSACTION keyword
37431        let strip_transaction = matches!(
37432            self.config.dialect,
37433            Some(DialectType::Snowflake)
37434                | Some(DialectType::PostgreSQL)
37435                | Some(DialectType::Redshift)
37436                | Some(DialectType::MySQL)
37437                | Some(DialectType::Hive)
37438                | Some(DialectType::Spark)
37439                | Some(DialectType::Databricks)
37440                | Some(DialectType::DuckDB)
37441                | Some(DialectType::Oracle)
37442                | Some(DialectType::Doris)
37443                | Some(DialectType::StarRocks)
37444                | Some(DialectType::Materialize)
37445                | Some(DialectType::ClickHouse)
37446        );
37447
37448        if is_start || use_start_transaction {
37449            // START TRANSACTION [modes]
37450            self.write_keyword("START TRANSACTION");
37451            if let Some(modes) = &e.modes {
37452                self.write_space();
37453                self.generate_expression(modes)?;
37454            }
37455        } else {
37456            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
37457            self.write_keyword("BEGIN");
37458
37459            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
37460            let is_kind = e.this.as_ref().map_or(false, |t| {
37461                if let Expression::Identifier(id) = t.as_ref() {
37462                    id.name.eq_ignore_ascii_case("DEFERRED")
37463                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
37464                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
37465                } else {
37466                    false
37467                }
37468            });
37469
37470            // Output kind before TRANSACTION keyword
37471            if is_kind {
37472                if let Some(this) = &e.this {
37473                    self.write_space();
37474                    if let Expression::Identifier(id) = this.as_ref() {
37475                        self.write_keyword(&id.name);
37476                    }
37477                }
37478            }
37479
37480            // Output TRANSACTION keyword if it was present and target supports it
37481            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
37482                self.write_space();
37483                self.write_keyword("TRANSACTION");
37484            }
37485
37486            // Output transaction name (not kind)
37487            if !is_kind {
37488                if let Some(this) = &e.this {
37489                    self.write_space();
37490                    self.generate_expression(this)?;
37491                }
37492            }
37493
37494            // Output WITH MARK 'description' for TSQL
37495            if has_with_mark {
37496                self.write_space();
37497                self.write_keyword("WITH MARK");
37498                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
37499                    if let Literal::String(desc) = lit.as_ref() {
37500                        if !desc.is_empty() {
37501                            self.write_space();
37502                            self.write(&format!("'{}'", desc));
37503                        }
37504                    }
37505                }
37506            }
37507
37508            // Output modes (isolation levels, etc.)
37509            if let Some(modes) = &e.modes {
37510                self.write_space();
37511                self.generate_expression(modes)?;
37512            }
37513        }
37514        Ok(())
37515    }
37516
37517    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
37518        // TRANSFORM(this, expression)
37519        self.write_keyword("TRANSFORM");
37520        self.write("(");
37521        self.generate_expression(&e.this)?;
37522        self.write(", ");
37523        self.generate_expression(&e.expression)?;
37524        self.write(")");
37525        Ok(())
37526    }
37527
37528    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
37529        // TRANSFORM(expressions)
37530        self.write_keyword("TRANSFORM");
37531        self.write("(");
37532        if self.config.pretty && !e.expressions.is_empty() {
37533            self.indent_level += 1;
37534            for (i, expr) in e.expressions.iter().enumerate() {
37535                if i > 0 {
37536                    self.write(",");
37537                }
37538                self.write_newline();
37539                self.write_indent();
37540                self.generate_expression(expr)?;
37541            }
37542            self.indent_level -= 1;
37543            self.write_newline();
37544            self.write(")");
37545        } else {
37546            for (i, expr) in e.expressions.iter().enumerate() {
37547                if i > 0 {
37548                    self.write(", ");
37549                }
37550                self.generate_expression(expr)?;
37551            }
37552            self.write(")");
37553        }
37554        Ok(())
37555    }
37556
37557    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
37558        use crate::dialects::DialectType;
37559        // TRANSIENT is Snowflake-specific; skip for other dialects
37560        if let Some(this) = &e.this {
37561            self.generate_expression(this)?;
37562            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
37563                self.write_space();
37564            }
37565        }
37566        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
37567            self.write_keyword("TRANSIENT");
37568        }
37569        Ok(())
37570    }
37571
37572    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
37573        // TRANSLATE(this, from_, to)
37574        self.write_keyword("TRANSLATE");
37575        self.write("(");
37576        self.generate_expression(&e.this)?;
37577        if let Some(from) = &e.from_ {
37578            self.write(", ");
37579            self.generate_expression(from)?;
37580        }
37581        if let Some(to) = &e.to {
37582            self.write(", ");
37583            self.generate_expression(to)?;
37584        }
37585        self.write(")");
37586        Ok(())
37587    }
37588
37589    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
37590        // TRANSLATE(this USING expression)
37591        self.write_keyword("TRANSLATE");
37592        self.write("(");
37593        self.generate_expression(&e.this)?;
37594        self.write_space();
37595        self.write_keyword("USING");
37596        self.write_space();
37597        self.generate_expression(&e.expression)?;
37598        if e.with_error.is_some() {
37599            self.write_space();
37600            self.write_keyword("WITH ERROR");
37601        }
37602        self.write(")");
37603        Ok(())
37604    }
37605
37606    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
37607        // TRUNCATE TABLE table1, table2, ...
37608        self.write_keyword("TRUNCATE TABLE");
37609        self.write_space();
37610        for (i, expr) in e.expressions.iter().enumerate() {
37611            if i > 0 {
37612                self.write(", ");
37613            }
37614            self.generate_expression(expr)?;
37615        }
37616        Ok(())
37617    }
37618
37619    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
37620        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
37621        self.write_keyword("TRY_BASE64_DECODE_BINARY");
37622        self.write("(");
37623        self.generate_expression(&e.this)?;
37624        if let Some(alphabet) = &e.alphabet {
37625            self.write(", ");
37626            self.generate_expression(alphabet)?;
37627        }
37628        self.write(")");
37629        Ok(())
37630    }
37631
37632    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
37633        // TRY_BASE64_DECODE_STRING(this, [alphabet])
37634        self.write_keyword("TRY_BASE64_DECODE_STRING");
37635        self.write("(");
37636        self.generate_expression(&e.this)?;
37637        if let Some(alphabet) = &e.alphabet {
37638            self.write(", ");
37639            self.generate_expression(alphabet)?;
37640        }
37641        self.write(")");
37642        Ok(())
37643    }
37644
37645    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
37646        // TRY_TO_DECFLOAT(this, [format])
37647        self.write_keyword("TRY_TO_DECFLOAT");
37648        self.write("(");
37649        self.generate_expression(&e.this)?;
37650        if let Some(format) = &e.format {
37651            self.write(", '");
37652            self.write(format);
37653            self.write("'");
37654        }
37655        self.write(")");
37656        Ok(())
37657    }
37658
37659    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
37660        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
37661        self.write_keyword("TS_OR_DS_ADD");
37662        self.write("(");
37663        self.generate_expression(&e.this)?;
37664        self.write(", ");
37665        self.generate_expression(&e.expression)?;
37666        if let Some(unit) = &e.unit {
37667            self.write(", ");
37668            self.write_keyword(unit);
37669        }
37670        if let Some(return_type) = &e.return_type {
37671            self.write(", ");
37672            self.generate_expression(return_type)?;
37673        }
37674        self.write(")");
37675        Ok(())
37676    }
37677
37678    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
37679        // TS_OR_DS_DIFF(this, expression, [unit])
37680        self.write_keyword("TS_OR_DS_DIFF");
37681        self.write("(");
37682        self.generate_expression(&e.this)?;
37683        self.write(", ");
37684        self.generate_expression(&e.expression)?;
37685        if let Some(unit) = &e.unit {
37686            self.write(", ");
37687            self.write_keyword(unit);
37688        }
37689        self.write(")");
37690        Ok(())
37691    }
37692
37693    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
37694        let default_time_format = "%Y-%m-%d %H:%M:%S";
37695        let default_date_format = "%Y-%m-%d";
37696        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
37697            f != default_time_format && f != default_date_format
37698        });
37699
37700        if has_non_default_format {
37701            // With non-default format: dialect-specific handling
37702            let fmt = e.format.as_ref().unwrap();
37703            match self.config.dialect {
37704                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
37705                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
37706                    // STR_TO_DATE is the MySQL-native form of StrToTime
37707                    let str_to_time = crate::expressions::StrToTime {
37708                        this: Box::new((*e.this).clone()),
37709                        format: fmt.clone(),
37710                        zone: None,
37711                        safe: None,
37712                        target_type: None,
37713                    };
37714                    self.generate_str_to_time(&str_to_time)?;
37715                }
37716                Some(DialectType::Hive)
37717                | Some(DialectType::Spark)
37718                | Some(DialectType::Databricks) => {
37719                    // Hive/Spark: TO_DATE(x, java_fmt)
37720                    self.write_keyword("TO_DATE");
37721                    self.write("(");
37722                    self.generate_expression(&e.this)?;
37723                    self.write(", '");
37724                    self.write(&Self::strftime_to_java_format(fmt));
37725                    self.write("')");
37726                }
37727                Some(DialectType::Snowflake) => {
37728                    // Snowflake: TO_DATE(x, snowflake_fmt)
37729                    self.write_keyword("TO_DATE");
37730                    self.write("(");
37731                    self.generate_expression(&e.this)?;
37732                    self.write(", '");
37733                    self.write(&Self::strftime_to_snowflake_format(fmt));
37734                    self.write("')");
37735                }
37736                Some(DialectType::Doris) => {
37737                    // Doris: TO_DATE(x) - ignores format
37738                    self.write_keyword("TO_DATE");
37739                    self.write("(");
37740                    self.generate_expression(&e.this)?;
37741                    self.write(")");
37742                }
37743                _ => {
37744                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
37745                    self.write_keyword("CAST");
37746                    self.write("(");
37747                    let str_to_time = crate::expressions::StrToTime {
37748                        this: Box::new((*e.this).clone()),
37749                        format: fmt.clone(),
37750                        zone: None,
37751                        safe: None,
37752                        target_type: None,
37753                    };
37754                    self.generate_str_to_time(&str_to_time)?;
37755                    self.write_keyword(" AS ");
37756                    self.write_keyword("DATE");
37757                    self.write(")");
37758                }
37759            }
37760        } else {
37761            // Without format (or default format): simple date conversion
37762            match self.config.dialect {
37763                Some(DialectType::MySQL)
37764                | Some(DialectType::SQLite)
37765                | Some(DialectType::StarRocks) => {
37766                    // MySQL/SQLite/StarRocks: DATE(x)
37767                    self.write_keyword("DATE");
37768                    self.write("(");
37769                    self.generate_expression(&e.this)?;
37770                    self.write(")");
37771                }
37772                Some(DialectType::Hive)
37773                | Some(DialectType::Spark)
37774                | Some(DialectType::Databricks)
37775                | Some(DialectType::Snowflake)
37776                | Some(DialectType::Doris) => {
37777                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
37778                    self.write_keyword("TO_DATE");
37779                    self.write("(");
37780                    self.generate_expression(&e.this)?;
37781                    self.write(")");
37782                }
37783                Some(DialectType::Presto)
37784                | Some(DialectType::Trino)
37785                | Some(DialectType::Athena) => {
37786                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
37787                    self.write_keyword("CAST");
37788                    self.write("(");
37789                    self.write_keyword("CAST");
37790                    self.write("(");
37791                    self.generate_expression(&e.this)?;
37792                    self.write_keyword(" AS ");
37793                    self.write_keyword("TIMESTAMP");
37794                    self.write(")");
37795                    self.write_keyword(" AS ");
37796                    self.write_keyword("DATE");
37797                    self.write(")");
37798                }
37799                Some(DialectType::ClickHouse) => {
37800                    // ClickHouse: CAST(x AS Nullable(DATE))
37801                    self.write_keyword("CAST");
37802                    self.write("(");
37803                    self.generate_expression(&e.this)?;
37804                    self.write_keyword(" AS ");
37805                    self.write("Nullable(DATE)");
37806                    self.write(")");
37807                }
37808                _ => {
37809                    // Default: CAST(x AS DATE)
37810                    self.write_keyword("CAST");
37811                    self.write("(");
37812                    self.generate_expression(&e.this)?;
37813                    self.write_keyword(" AS ");
37814                    self.write_keyword("DATE");
37815                    self.write(")");
37816                }
37817            }
37818        }
37819        Ok(())
37820    }
37821
37822    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
37823        // TS_OR_DS_TO_TIME(this, [format])
37824        self.write_keyword("TS_OR_DS_TO_TIME");
37825        self.write("(");
37826        self.generate_expression(&e.this)?;
37827        if let Some(format) = &e.format {
37828            self.write(", '");
37829            self.write(format);
37830            self.write("'");
37831        }
37832        self.write(")");
37833        Ok(())
37834    }
37835
37836    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
37837        // UNHEX(this, [expression])
37838        self.write_keyword("UNHEX");
37839        self.write("(");
37840        self.generate_expression(&e.this)?;
37841        if let Some(expression) = &e.expression {
37842            self.write(", ");
37843            self.generate_expression(expression)?;
37844        }
37845        self.write(")");
37846        Ok(())
37847    }
37848
37849    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
37850        // U&this [UESCAPE escape]
37851        self.write("U&");
37852        self.generate_expression(&e.this)?;
37853        if let Some(escape) = &e.escape {
37854            self.write_space();
37855            self.write_keyword("UESCAPE");
37856            self.write_space();
37857            self.generate_expression(escape)?;
37858        }
37859        Ok(())
37860    }
37861
37862    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
37863        // UNIFORM(this, expression, [gen], [seed])
37864        self.write_keyword("UNIFORM");
37865        self.write("(");
37866        self.generate_expression(&e.this)?;
37867        self.write(", ");
37868        self.generate_expression(&e.expression)?;
37869        if let Some(gen) = &e.gen {
37870            self.write(", ");
37871            self.generate_expression(gen)?;
37872        }
37873        if let Some(seed) = &e.seed {
37874            self.write(", ");
37875            self.generate_expression(seed)?;
37876        }
37877        self.write(")");
37878        Ok(())
37879    }
37880
37881    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
37882        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
37883        self.write_keyword("UNIQUE");
37884        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
37885        if e.nulls.is_some() {
37886            self.write(" NULLS NOT DISTINCT");
37887        }
37888        if let Some(this) = &e.this {
37889            self.write_space();
37890            self.generate_expression(this)?;
37891        }
37892        if let Some(index_type) = &e.index_type {
37893            self.write(" USING ");
37894            self.generate_expression(index_type)?;
37895        }
37896        if let Some(on_conflict) = &e.on_conflict {
37897            self.write_space();
37898            self.generate_expression(on_conflict)?;
37899        }
37900        for opt in &e.options {
37901            self.write_space();
37902            self.generate_expression(opt)?;
37903        }
37904        Ok(())
37905    }
37906
37907    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
37908        // UNIQUE KEY (expressions)
37909        self.write_keyword("UNIQUE KEY");
37910        self.write(" (");
37911        for (i, expr) in e.expressions.iter().enumerate() {
37912            if i > 0 {
37913                self.write(", ");
37914            }
37915            self.generate_expression(expr)?;
37916        }
37917        self.write(")");
37918        Ok(())
37919    }
37920
37921    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
37922        // ROLLUP (r1(col1, col2), r2(col1))
37923        self.write_keyword("ROLLUP");
37924        self.write(" (");
37925        for (i, index) in e.expressions.iter().enumerate() {
37926            if i > 0 {
37927                self.write(", ");
37928            }
37929            self.generate_identifier(&index.name)?;
37930            self.write("(");
37931            for (j, col) in index.expressions.iter().enumerate() {
37932                if j > 0 {
37933                    self.write(", ");
37934                }
37935                self.generate_identifier(col)?;
37936            }
37937            self.write(")");
37938        }
37939        self.write(")");
37940        Ok(())
37941    }
37942
37943    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
37944        match self.config.dialect {
37945            Some(DialectType::DuckDB) => {
37946                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
37947                self.write_keyword("STRFTIME");
37948                self.write("(");
37949                self.write_keyword("TO_TIMESTAMP");
37950                self.write("(");
37951                self.generate_expression(&e.this)?;
37952                self.write("), '");
37953                if let Some(format) = &e.format {
37954                    self.write(format);
37955                }
37956                self.write("')");
37957            }
37958            Some(DialectType::Hive) => {
37959                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
37960                self.write_keyword("FROM_UNIXTIME");
37961                self.write("(");
37962                self.generate_expression(&e.this)?;
37963                if let Some(format) = &e.format {
37964                    if format != "yyyy-MM-dd HH:mm:ss" {
37965                        self.write(", '");
37966                        self.write(format);
37967                        self.write("'");
37968                    }
37969                }
37970                self.write(")");
37971            }
37972            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37973                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
37974                self.write_keyword("DATE_FORMAT");
37975                self.write("(");
37976                self.write_keyword("FROM_UNIXTIME");
37977                self.write("(");
37978                self.generate_expression(&e.this)?;
37979                self.write("), '");
37980                if let Some(format) = &e.format {
37981                    self.write(format);
37982                }
37983                self.write("')");
37984            }
37985            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37986                // Spark: FROM_UNIXTIME(value, format)
37987                self.write_keyword("FROM_UNIXTIME");
37988                self.write("(");
37989                self.generate_expression(&e.this)?;
37990                if let Some(format) = &e.format {
37991                    self.write(", '");
37992                    self.write(format);
37993                    self.write("'");
37994                }
37995                self.write(")");
37996            }
37997            _ => {
37998                // Default: UNIX_TO_STR(this, [format])
37999                self.write_keyword("UNIX_TO_STR");
38000                self.write("(");
38001                self.generate_expression(&e.this)?;
38002                if let Some(format) = &e.format {
38003                    self.write(", '");
38004                    self.write(format);
38005                    self.write("'");
38006                }
38007                self.write(")");
38008            }
38009        }
38010        Ok(())
38011    }
38012
38013    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
38014        use crate::dialects::DialectType;
38015        let scale = e.scale.unwrap_or(0); // 0 = seconds
38016
38017        match self.config.dialect {
38018            Some(DialectType::Snowflake) => {
38019                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
38020                self.write_keyword("TO_TIMESTAMP");
38021                self.write("(");
38022                self.generate_expression(&e.this)?;
38023                if let Some(s) = e.scale {
38024                    if s > 0 {
38025                        self.write(", ");
38026                        self.write(&s.to_string());
38027                    }
38028                }
38029                self.write(")");
38030            }
38031            Some(DialectType::BigQuery) => {
38032                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
38033                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
38034                match scale {
38035                    0 => {
38036                        self.write_keyword("TIMESTAMP_SECONDS");
38037                        self.write("(");
38038                        self.generate_expression(&e.this)?;
38039                        self.write(")");
38040                    }
38041                    3 => {
38042                        self.write_keyword("TIMESTAMP_MILLIS");
38043                        self.write("(");
38044                        self.generate_expression(&e.this)?;
38045                        self.write(")");
38046                    }
38047                    6 => {
38048                        self.write_keyword("TIMESTAMP_MICROS");
38049                        self.write("(");
38050                        self.generate_expression(&e.this)?;
38051                        self.write(")");
38052                    }
38053                    _ => {
38054                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
38055                        self.write_keyword("TIMESTAMP_SECONDS");
38056                        self.write("(CAST(");
38057                        self.generate_expression(&e.this)?;
38058                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
38059                    }
38060                }
38061            }
38062            Some(DialectType::Spark) => {
38063                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
38064                // TIMESTAMP_MILLIS(value) for scale=3
38065                // TIMESTAMP_MICROS(value) for scale=6
38066                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
38067                match scale {
38068                    0 => {
38069                        self.write_keyword("CAST");
38070                        self.write("(");
38071                        self.write_keyword("FROM_UNIXTIME");
38072                        self.write("(");
38073                        self.generate_expression(&e.this)?;
38074                        self.write(") ");
38075                        self.write_keyword("AS TIMESTAMP");
38076                        self.write(")");
38077                    }
38078                    3 => {
38079                        self.write_keyword("TIMESTAMP_MILLIS");
38080                        self.write("(");
38081                        self.generate_expression(&e.this)?;
38082                        self.write(")");
38083                    }
38084                    6 => {
38085                        self.write_keyword("TIMESTAMP_MICROS");
38086                        self.write("(");
38087                        self.generate_expression(&e.this)?;
38088                        self.write(")");
38089                    }
38090                    _ => {
38091                        self.write_keyword("TIMESTAMP_SECONDS");
38092                        self.write("(");
38093                        self.generate_expression(&e.this)?;
38094                        self.write(&format!(" / POWER(10, {}))", scale));
38095                    }
38096                }
38097            }
38098            Some(DialectType::Databricks) => {
38099                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
38100                // TIMESTAMP_MILLIS(value) for scale=3
38101                // TIMESTAMP_MICROS(value) for scale=6
38102                match scale {
38103                    0 => {
38104                        self.write_keyword("CAST");
38105                        self.write("(");
38106                        self.write_keyword("FROM_UNIXTIME");
38107                        self.write("(");
38108                        self.generate_expression(&e.this)?;
38109                        self.write(") ");
38110                        self.write_keyword("AS TIMESTAMP");
38111                        self.write(")");
38112                    }
38113                    3 => {
38114                        self.write_keyword("TIMESTAMP_MILLIS");
38115                        self.write("(");
38116                        self.generate_expression(&e.this)?;
38117                        self.write(")");
38118                    }
38119                    6 => {
38120                        self.write_keyword("TIMESTAMP_MICROS");
38121                        self.write("(");
38122                        self.generate_expression(&e.this)?;
38123                        self.write(")");
38124                    }
38125                    _ => {
38126                        self.write_keyword("TIMESTAMP_SECONDS");
38127                        self.write("(");
38128                        self.generate_expression(&e.this)?;
38129                        self.write(&format!(" / POWER(10, {}))", scale));
38130                    }
38131                }
38132            }
38133            Some(DialectType::Hive) => {
38134                // Hive: FROM_UNIXTIME(value)
38135                if scale == 0 {
38136                    self.write_keyword("FROM_UNIXTIME");
38137                    self.write("(");
38138                    self.generate_expression(&e.this)?;
38139                    self.write(")");
38140                } else {
38141                    self.write_keyword("FROM_UNIXTIME");
38142                    self.write("(");
38143                    self.generate_expression(&e.this)?;
38144                    self.write(&format!(" / POWER(10, {})", scale));
38145                    self.write(")");
38146                }
38147            }
38148            Some(DialectType::Presto) | Some(DialectType::Trino) => {
38149                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
38150                // FROM_UNIXTIME(value) for scale=0
38151                if scale == 0 {
38152                    self.write_keyword("FROM_UNIXTIME");
38153                    self.write("(");
38154                    self.generate_expression(&e.this)?;
38155                    self.write(")");
38156                } else {
38157                    self.write_keyword("FROM_UNIXTIME");
38158                    self.write("(CAST(");
38159                    self.generate_expression(&e.this)?;
38160                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
38161                }
38162            }
38163            Some(DialectType::DuckDB) => {
38164                // DuckDB: TO_TIMESTAMP(value) for scale=0
38165                // EPOCH_MS(value) for scale=3
38166                // MAKE_TIMESTAMP(value) for scale=6
38167                match scale {
38168                    0 => {
38169                        self.write_keyword("TO_TIMESTAMP");
38170                        self.write("(");
38171                        self.generate_expression(&e.this)?;
38172                        self.write(")");
38173                    }
38174                    3 => {
38175                        self.write_keyword("EPOCH_MS");
38176                        self.write("(");
38177                        self.generate_expression(&e.this)?;
38178                        self.write(")");
38179                    }
38180                    6 => {
38181                        self.write_keyword("MAKE_TIMESTAMP");
38182                        self.write("(");
38183                        self.generate_expression(&e.this)?;
38184                        self.write(")");
38185                    }
38186                    _ => {
38187                        self.write_keyword("TO_TIMESTAMP");
38188                        self.write("(");
38189                        self.generate_expression(&e.this)?;
38190                        self.write(&format!(" / POWER(10, {}))", scale));
38191                        self.write_keyword(" AT TIME ZONE");
38192                        self.write(" 'UTC'");
38193                    }
38194                }
38195            }
38196            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
38197                // Doris/StarRocks: FROM_UNIXTIME(value)
38198                self.write_keyword("FROM_UNIXTIME");
38199                self.write("(");
38200                self.generate_expression(&e.this)?;
38201                self.write(")");
38202            }
38203            Some(DialectType::Oracle) => {
38204                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
38205                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
38206                self.generate_expression(&e.this)?;
38207                self.write(" / 86400)");
38208            }
38209            Some(DialectType::Redshift) => {
38210                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
38211                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
38212                self.write("(TIMESTAMP 'epoch' + ");
38213                if scale == 0 {
38214                    self.generate_expression(&e.this)?;
38215                } else {
38216                    self.write("(");
38217                    self.generate_expression(&e.this)?;
38218                    self.write(&format!(" / POWER(10, {}))", scale));
38219                }
38220                self.write(" * INTERVAL '1 SECOND')");
38221            }
38222            Some(DialectType::Exasol) => {
38223                // Exasol: FROM_POSIX_TIME(value)
38224                self.write_keyword("FROM_POSIX_TIME");
38225                self.write("(");
38226                self.generate_expression(&e.this)?;
38227                self.write(")");
38228            }
38229            _ => {
38230                // Default: TO_TIMESTAMP(value[, scale])
38231                self.write_keyword("TO_TIMESTAMP");
38232                self.write("(");
38233                self.generate_expression(&e.this)?;
38234                if let Some(s) = e.scale {
38235                    self.write(", ");
38236                    self.write(&s.to_string());
38237                }
38238                self.write(")");
38239            }
38240        }
38241        Ok(())
38242    }
38243
38244    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
38245        // NAME col VALUE col1, col2, ...
38246        if !matches!(&*e.this, Expression::Null(_)) {
38247            self.write_keyword("NAME");
38248            self.write_space();
38249            self.generate_expression(&e.this)?;
38250        }
38251        if !e.expressions.is_empty() {
38252            self.write_space();
38253            self.write_keyword("VALUE");
38254            self.write_space();
38255            for (i, expr) in e.expressions.iter().enumerate() {
38256                if i > 0 {
38257                    self.write(", ");
38258                }
38259                self.generate_expression(expr)?;
38260            }
38261        }
38262        Ok(())
38263    }
38264
38265    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
38266        // this(expressions) or (this)(expressions)
38267        if e.wrapped.is_some() {
38268            self.write("(");
38269        }
38270        self.generate_expression(&e.this)?;
38271        if e.wrapped.is_some() {
38272            self.write(")");
38273        }
38274        self.write("(");
38275        for (i, expr) in e.expressions.iter().enumerate() {
38276            if i > 0 {
38277                self.write(", ");
38278            }
38279            self.generate_expression(expr)?;
38280        }
38281        self.write(")");
38282        Ok(())
38283    }
38284
38285    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
38286        // USING TEMPLATE this
38287        self.write_keyword("USING TEMPLATE");
38288        self.write_space();
38289        self.generate_expression(&e.this)?;
38290        Ok(())
38291    }
38292
38293    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
38294        // UTC_TIME
38295        self.write_keyword("UTC_TIME");
38296        Ok(())
38297    }
38298
38299    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
38300        if matches!(
38301            self.config.dialect,
38302            Some(crate::dialects::DialectType::ClickHouse)
38303        ) {
38304            self.write_keyword("CURRENT_TIMESTAMP");
38305            self.write("('UTC')");
38306        } else {
38307            self.write_keyword("UTC_TIMESTAMP");
38308        }
38309        Ok(())
38310    }
38311
38312    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
38313        use crate::dialects::DialectType;
38314        // Choose UUID function name based on target dialect
38315        let func_name = match self.config.dialect {
38316            Some(DialectType::Snowflake) => "UUID_STRING",
38317            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
38318            Some(DialectType::BigQuery) => "GENERATE_UUID",
38319            _ => {
38320                if let Some(name) = &e.name {
38321                    name.as_str()
38322                } else {
38323                    "UUID"
38324                }
38325            }
38326        };
38327        self.write_keyword(func_name);
38328        self.write("(");
38329        if let Some(this) = &e.this {
38330            self.generate_expression(this)?;
38331        }
38332        self.write(")");
38333        Ok(())
38334    }
38335
38336    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
38337        // MAP(key1, value1, key2, value2, ...)
38338        self.write_keyword("MAP");
38339        self.write("(");
38340        let mut first = true;
38341        for (k, v) in e.keys.iter().zip(e.values.iter()) {
38342            if !first {
38343                self.write(", ");
38344            }
38345            self.generate_expression(k)?;
38346            self.write(", ");
38347            self.generate_expression(v)?;
38348            first = false;
38349        }
38350        self.write(")");
38351        Ok(())
38352    }
38353
38354    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
38355        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
38356        self.write_keyword("VECTOR_SEARCH");
38357        self.write("(");
38358        self.generate_expression(&e.this)?;
38359        if let Some(col) = &e.column_to_search {
38360            self.write(", ");
38361            self.generate_expression(col)?;
38362        }
38363        if let Some(query_table) = &e.query_table {
38364            self.write(", ");
38365            self.generate_expression(query_table)?;
38366        }
38367        if let Some(query_col) = &e.query_column_to_search {
38368            self.write(", ");
38369            self.generate_expression(query_col)?;
38370        }
38371        if let Some(top_k) = &e.top_k {
38372            self.write(", ");
38373            self.generate_expression(top_k)?;
38374        }
38375        if let Some(dist_type) = &e.distance_type {
38376            self.write(", ");
38377            self.generate_expression(dist_type)?;
38378        }
38379        self.write(")");
38380        Ok(())
38381    }
38382
38383    fn generate_version(&mut self, e: &Version) -> Result<()> {
38384        // Python: f"FOR {expression.name} {kind} {expr}"
38385        // e.this = Identifier("TIMESTAMP" or "VERSION")
38386        // e.kind = "AS OF" (or "BETWEEN", etc.)
38387        // e.expression = the value expression
38388        // Hive does NOT use the FOR prefix for time travel
38389        use crate::dialects::DialectType;
38390        let skip_for = matches!(
38391            self.config.dialect,
38392            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
38393        );
38394        if !skip_for {
38395            self.write_keyword("FOR");
38396            self.write_space();
38397        }
38398        // Extract the name from this (which is an Identifier expression)
38399        match e.this.as_ref() {
38400            Expression::Identifier(ident) => {
38401                self.write_keyword(&ident.name);
38402            }
38403            _ => {
38404                self.generate_expression(&e.this)?;
38405            }
38406        }
38407        self.write_space();
38408        self.write_keyword(&e.kind);
38409        if let Some(expression) = &e.expression {
38410            self.write_space();
38411            self.generate_expression(expression)?;
38412        }
38413        Ok(())
38414    }
38415
38416    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
38417        // Python: return self.sql(expression, "this")
38418        self.generate_expression(&e.this)?;
38419        Ok(())
38420    }
38421
38422    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
38423        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
38424        if e.this.is_some() {
38425            self.write_keyword("NOT VOLATILE");
38426        } else {
38427            self.write_keyword("VOLATILE");
38428        }
38429        Ok(())
38430    }
38431
38432    fn generate_watermark_column_constraint(
38433        &mut self,
38434        e: &WatermarkColumnConstraint,
38435    ) -> Result<()> {
38436        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
38437        self.write_keyword("WATERMARK FOR");
38438        self.write_space();
38439        self.generate_expression(&e.this)?;
38440        self.write_space();
38441        self.write_keyword("AS");
38442        self.write_space();
38443        self.generate_expression(&e.expression)?;
38444        Ok(())
38445    }
38446
38447    fn generate_week(&mut self, e: &Week) -> Result<()> {
38448        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
38449        self.write_keyword("WEEK");
38450        self.write("(");
38451        self.generate_expression(&e.this)?;
38452        if let Some(mode) = &e.mode {
38453            self.write(", ");
38454            self.generate_expression(mode)?;
38455        }
38456        self.write(")");
38457        Ok(())
38458    }
38459
38460    fn generate_when(&mut self, e: &When) -> Result<()> {
38461        // Python: WHEN {matched}{source}{condition} THEN {then}
38462        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
38463        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
38464        self.write_keyword("WHEN");
38465        self.write_space();
38466
38467        // Check if matched
38468        if let Some(matched) = &e.matched {
38469            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
38470            match matched.as_ref() {
38471                Expression::Boolean(b) if b.value => {
38472                    self.write_keyword("MATCHED");
38473                }
38474                _ => {
38475                    self.write_keyword("NOT MATCHED");
38476                }
38477            }
38478        } else {
38479            self.write_keyword("NOT MATCHED");
38480        }
38481
38482        // BY SOURCE / BY TARGET
38483        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
38484        // BY TARGET is the default and typically omitted in output
38485        // Only emit if the dialect supports BY SOURCE syntax
38486        if self.config.matched_by_source {
38487            if let Some(source) = &e.source {
38488                if let Expression::Boolean(b) = source.as_ref() {
38489                    if b.value {
38490                        // BY SOURCE
38491                        self.write_space();
38492                        self.write_keyword("BY SOURCE");
38493                    }
38494                    // BY TARGET (b.value == false) is omitted as it's the default
38495                } else {
38496                    // For non-boolean source, output as BY SOURCE (legacy behavior)
38497                    self.write_space();
38498                    self.write_keyword("BY SOURCE");
38499                }
38500            }
38501        }
38502
38503        // Condition
38504        if let Some(condition) = &e.condition {
38505            self.write_space();
38506            self.write_keyword("AND");
38507            self.write_space();
38508            self.generate_expression(condition)?;
38509        }
38510
38511        self.write_space();
38512        self.write_keyword("THEN");
38513        self.write_space();
38514
38515        // Generate the then expression (could be INSERT, UPDATE, DELETE)
38516        // MERGE actions are stored as Tuples with the action keyword as first element
38517        self.generate_merge_action(&e.then)?;
38518
38519        Ok(())
38520    }
38521
38522    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
38523        match action {
38524            Expression::Tuple(tuple) => {
38525                let elements = &tuple.expressions;
38526                if elements.is_empty() {
38527                    return self.generate_expression(action);
38528                }
38529                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
38530                match &elements[0] {
38531                    Expression::Var(v) if v.this == "INSERT" => {
38532                        self.write_keyword("INSERT");
38533                        // Spark: INSERT * (insert all columns)
38534                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
38535                            self.write(" *");
38536                            if let Some(Expression::Where(w)) = elements.get(2) {
38537                                self.write_space();
38538                                self.generate_where(w)?;
38539                            }
38540                        } else {
38541                            let mut values_idx = 1;
38542                            // Check if second element is column list (Tuple)
38543                            if elements.len() > 1 {
38544                                if let Expression::Tuple(cols) = &elements[1] {
38545                                    // Could be columns or values - if there's a third element, second is columns
38546                                    if elements.len() > 2 {
38547                                        // Second is columns, third is values
38548                                        self.write(" (");
38549                                        for (i, col) in cols.expressions.iter().enumerate() {
38550                                            if i > 0 {
38551                                                self.write(", ");
38552                                            }
38553                                            // Strip MERGE target qualifiers from INSERT column list
38554                                            if !self.merge_strip_qualifiers.is_empty() {
38555                                                let stripped = self.strip_merge_qualifier(col);
38556                                                self.generate_expression(&stripped)?;
38557                                            } else {
38558                                                self.generate_expression(col)?;
38559                                            }
38560                                        }
38561                                        self.write(")");
38562                                        values_idx = 2;
38563                                    } else {
38564                                        // Only two elements: INSERT + values (no explicit columns)
38565                                        values_idx = 1;
38566                                    }
38567                                }
38568                            }
38569                            let mut next_idx = values_idx;
38570                            // Generate VALUES clause
38571                            if values_idx < elements.len()
38572                                && !matches!(&elements[values_idx], Expression::Where(_))
38573                            {
38574                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
38575                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
38576                                if !is_row {
38577                                    self.write_space();
38578                                    self.write_keyword("VALUES");
38579                                }
38580                                self.write(" ");
38581                                if let Expression::Tuple(vals) = &elements[values_idx] {
38582                                    self.write("(");
38583                                    for (i, val) in vals.expressions.iter().enumerate() {
38584                                        if i > 0 {
38585                                            self.write(", ");
38586                                        }
38587                                        self.generate_expression(val)?;
38588                                    }
38589                                    self.write(")");
38590                                } else {
38591                                    self.generate_expression(&elements[values_idx])?;
38592                                }
38593                                next_idx += 1;
38594                            }
38595                            if let Some(Expression::Where(w)) = elements.get(next_idx) {
38596                                self.write_space();
38597                                self.generate_where(w)?;
38598                            }
38599                        } // close else for INSERT * check
38600                    }
38601                    Expression::Var(v) if v.this == "UPDATE" => {
38602                        self.write_keyword("UPDATE");
38603                        // Spark: UPDATE * (update all columns)
38604                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
38605                            self.write(" *");
38606                            if let Some(Expression::Where(w)) = elements.get(2) {
38607                                self.write_space();
38608                                self.generate_where(w)?;
38609                            }
38610                        } else if elements.len() > 1 {
38611                            self.write_space();
38612                            self.write_keyword("SET");
38613                            // In pretty mode, put assignments on next line with extra indent
38614                            if self.config.pretty {
38615                                self.write_newline();
38616                                self.indent_level += 1;
38617                                self.write_indent();
38618                            } else {
38619                                self.write_space();
38620                            }
38621                            if let Expression::Tuple(assignments) = &elements[1] {
38622                                for (i, assignment) in assignments.expressions.iter().enumerate() {
38623                                    if i > 0 {
38624                                        if self.config.pretty {
38625                                            self.write(",");
38626                                            self.write_newline();
38627                                            self.write_indent();
38628                                        } else {
38629                                            self.write(", ");
38630                                        }
38631                                    }
38632                                    // Strip MERGE target qualifiers from left side of UPDATE SET
38633                                    if !self.merge_strip_qualifiers.is_empty() {
38634                                        self.generate_merge_set_assignment(assignment)?;
38635                                    } else {
38636                                        self.generate_expression(assignment)?;
38637                                    }
38638                                }
38639                            } else {
38640                                self.generate_expression(&elements[1])?;
38641                            }
38642                            if self.config.pretty {
38643                                self.indent_level -= 1;
38644                            }
38645                            if let Some(Expression::Where(w)) = elements.get(2) {
38646                                self.write_space();
38647                                self.generate_where(w)?;
38648                            }
38649                        }
38650                    }
38651                    Expression::Var(v) if v.this == "DELETE" => {
38652                        self.write_keyword("DELETE");
38653                        if let Some(Expression::Where(w)) = elements.get(1) {
38654                            self.write_space();
38655                            self.generate_where(w)?;
38656                        }
38657                    }
38658                    _ => {
38659                        // Fallback: generic tuple generation
38660                        self.generate_expression(action)?;
38661                    }
38662                }
38663            }
38664            Expression::Var(v)
38665                if v.this == "INSERT"
38666                    || v.this == "UPDATE"
38667                    || v.this == "DELETE"
38668                    || v.this == "DO NOTHING" =>
38669            {
38670                self.write_keyword(&v.this);
38671            }
38672            _ => {
38673                self.generate_expression(action)?;
38674            }
38675        }
38676        Ok(())
38677    }
38678
38679    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
38680    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
38681        match assignment {
38682            Expression::Eq(eq) => {
38683                // Strip qualifier from the left side if it matches a MERGE target name
38684                let stripped_left = self.strip_merge_qualifier(&eq.left);
38685                self.generate_expression(&stripped_left)?;
38686                self.write(" = ");
38687                self.generate_expression(&eq.right)?;
38688                Ok(())
38689            }
38690            other => self.generate_expression(other),
38691        }
38692    }
38693
38694    /// Strip table qualifier from a column reference if it matches a MERGE target name
38695    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
38696        match expr {
38697            Expression::Column(col) => {
38698                if let Some(ref table_ident) = col.table {
38699                    if self
38700                        .merge_strip_qualifiers
38701                        .iter()
38702                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
38703                    {
38704                        // Strip the table qualifier
38705                        let mut col = col.clone();
38706                        col.table = None;
38707                        return Expression::Column(col);
38708                    }
38709                }
38710                expr.clone()
38711            }
38712            Expression::Dot(dot) => {
38713                // table.column -> column (strip qualifier)
38714                if let Expression::Identifier(id) = &dot.this {
38715                    if self
38716                        .merge_strip_qualifiers
38717                        .iter()
38718                        .any(|n| n.eq_ignore_ascii_case(&id.name))
38719                    {
38720                        return Expression::Identifier(dot.field.clone());
38721                    }
38722                }
38723                expr.clone()
38724            }
38725            _ => expr.clone(),
38726        }
38727    }
38728
38729    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
38730        // Python: return self.expressions(expression, sep=" ", indent=False)
38731        for (i, expr) in e.expressions.iter().enumerate() {
38732            if i > 0 {
38733                // In pretty mode, each WHEN clause on its own line
38734                if self.config.pretty {
38735                    self.write_newline();
38736                    self.write_indent();
38737                } else {
38738                    self.write_space();
38739                }
38740            }
38741            self.generate_expression(expr)?;
38742        }
38743        Ok(())
38744    }
38745
38746    fn generate_where(&mut self, e: &Where) -> Result<()> {
38747        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
38748        self.write_keyword("WHERE");
38749        self.write_space();
38750        self.generate_expression(&e.this)?;
38751        Ok(())
38752    }
38753
38754    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
38755        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
38756        self.write_keyword("WIDTH_BUCKET");
38757        self.write("(");
38758        self.generate_expression(&e.this)?;
38759        if let Some(min_value) = &e.min_value {
38760            self.write(", ");
38761            self.generate_expression(min_value)?;
38762        }
38763        if let Some(max_value) = &e.max_value {
38764            self.write(", ");
38765            self.generate_expression(max_value)?;
38766        }
38767        if let Some(num_buckets) = &e.num_buckets {
38768            self.write(", ");
38769            self.generate_expression(num_buckets)?;
38770        }
38771        self.write(")");
38772        Ok(())
38773    }
38774
38775    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
38776        // Window specification: PARTITION BY ... ORDER BY ... frame
38777        self.generate_window_spec(e)
38778    }
38779
38780    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
38781        // Window specification: PARTITION BY ... ORDER BY ... frame
38782        let mut has_content = false;
38783
38784        // PARTITION BY
38785        if !e.partition_by.is_empty() {
38786            self.write_keyword("PARTITION BY");
38787            self.write_space();
38788            for (i, expr) in e.partition_by.iter().enumerate() {
38789                if i > 0 {
38790                    self.write(", ");
38791                }
38792                self.generate_expression(expr)?;
38793            }
38794            has_content = true;
38795        }
38796
38797        // ORDER BY
38798        if !e.order_by.is_empty() {
38799            if has_content {
38800                self.write_space();
38801            }
38802            self.write_keyword("ORDER BY");
38803            self.write_space();
38804            for (i, ordered) in e.order_by.iter().enumerate() {
38805                if i > 0 {
38806                    self.write(", ");
38807                }
38808                self.generate_expression(&ordered.this)?;
38809                if ordered.desc {
38810                    self.write_space();
38811                    self.write_keyword("DESC");
38812                } else if ordered.explicit_asc {
38813                    self.write_space();
38814                    self.write_keyword("ASC");
38815                }
38816                if let Some(nulls_first) = ordered.nulls_first {
38817                    self.write_space();
38818                    self.write_keyword("NULLS");
38819                    self.write_space();
38820                    if nulls_first {
38821                        self.write_keyword("FIRST");
38822                    } else {
38823                        self.write_keyword("LAST");
38824                    }
38825                }
38826            }
38827            has_content = true;
38828        }
38829
38830        // Frame specification
38831        if let Some(frame) = &e.frame {
38832            if has_content {
38833                self.write_space();
38834            }
38835            self.generate_window_frame(frame)?;
38836        }
38837
38838        Ok(())
38839    }
38840
38841    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
38842        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
38843        self.write_keyword("WITH");
38844        self.write_space();
38845        if e.no.is_some() {
38846            self.write_keyword("NO");
38847            self.write_space();
38848        }
38849        self.write_keyword("DATA");
38850
38851        // statistics
38852        if let Some(statistics) = &e.statistics {
38853            self.write_space();
38854            self.write_keyword("AND");
38855            self.write_space();
38856            // Check if statistics is true or false
38857            match statistics.as_ref() {
38858                Expression::Boolean(b) if !b.value => {
38859                    self.write_keyword("NO");
38860                    self.write_space();
38861                }
38862                _ => {}
38863            }
38864            self.write_keyword("STATISTICS");
38865        }
38866        Ok(())
38867    }
38868
38869    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
38870        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
38871        self.write_keyword("WITH FILL");
38872
38873        if let Some(from_) = &e.from_ {
38874            self.write_space();
38875            self.write_keyword("FROM");
38876            self.write_space();
38877            self.generate_expression(from_)?;
38878        }
38879
38880        if let Some(to) = &e.to {
38881            self.write_space();
38882            self.write_keyword("TO");
38883            self.write_space();
38884            self.generate_expression(to)?;
38885        }
38886
38887        if let Some(step) = &e.step {
38888            self.write_space();
38889            self.write_keyword("STEP");
38890            self.write_space();
38891            self.generate_expression(step)?;
38892        }
38893
38894        if let Some(staleness) = &e.staleness {
38895            self.write_space();
38896            self.write_keyword("STALENESS");
38897            self.write_space();
38898            self.generate_expression(staleness)?;
38899        }
38900
38901        if let Some(interpolate) = &e.interpolate {
38902            self.write_space();
38903            self.write_keyword("INTERPOLATE");
38904            self.write(" (");
38905            // INTERPOLATE items use reversed alias format: name AS expression
38906            self.generate_interpolate_item(interpolate)?;
38907            self.write(")");
38908        }
38909
38910        Ok(())
38911    }
38912
38913    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
38914    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
38915        match expr {
38916            Expression::Alias(alias) => {
38917                // Output as: alias_name AS expression
38918                self.generate_identifier(&alias.alias)?;
38919                self.write_space();
38920                self.write_keyword("AS");
38921                self.write_space();
38922                self.generate_expression(&alias.this)?;
38923            }
38924            Expression::Tuple(tuple) => {
38925                for (i, item) in tuple.expressions.iter().enumerate() {
38926                    if i > 0 {
38927                        self.write(", ");
38928                    }
38929                    self.generate_interpolate_item(item)?;
38930                }
38931            }
38932            other => {
38933                self.generate_expression(other)?;
38934            }
38935        }
38936        Ok(())
38937    }
38938
38939    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
38940        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
38941        self.write_keyword("WITH JOURNAL TABLE");
38942        self.write("=");
38943        self.generate_expression(&e.this)?;
38944        Ok(())
38945    }
38946
38947    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
38948        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
38949        self.generate_expression(&e.this)?;
38950        self.write_space();
38951        self.write_keyword("WITH");
38952        self.write_space();
38953        self.write_keyword(&e.op);
38954        Ok(())
38955    }
38956
38957    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
38958        // Python: return f"WITH {self.expressions(expression, flat=True)}"
38959        self.write_keyword("WITH");
38960        self.write_space();
38961        for (i, expr) in e.expressions.iter().enumerate() {
38962            if i > 0 {
38963                self.write(", ");
38964            }
38965            self.generate_expression(expr)?;
38966        }
38967        Ok(())
38968    }
38969
38970    fn generate_with_schema_binding_property(
38971        &mut self,
38972        e: &WithSchemaBindingProperty,
38973    ) -> Result<()> {
38974        // Python: return f"WITH {self.sql(expression, 'this')}"
38975        self.write_keyword("WITH");
38976        self.write_space();
38977        self.generate_expression(&e.this)?;
38978        Ok(())
38979    }
38980
38981    fn generate_with_system_versioning_property(
38982        &mut self,
38983        e: &WithSystemVersioningProperty,
38984    ) -> Result<()> {
38985        // Python: complex logic for SYSTEM_VERSIONING with options
38986        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
38987        // or SYSTEM_VERSIONING=ON/OFF
38988        // with WITH(...) wrapper if with_ is set
38989
38990        let mut parts = Vec::new();
38991
38992        if let Some(this) = &e.this {
38993            // HISTORY_TABLE=...
38994            let mut s = String::from("HISTORY_TABLE=");
38995            let mut gen = Generator::new();
38996            gen.generate_expression(this)?;
38997            s.push_str(&gen.output);
38998            parts.push(s);
38999        }
39000
39001        if let Some(data_consistency) = &e.data_consistency {
39002            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
39003            let mut gen = Generator::new();
39004            gen.generate_expression(data_consistency)?;
39005            s.push_str(&gen.output);
39006            parts.push(s);
39007        }
39008
39009        if let Some(retention_period) = &e.retention_period {
39010            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
39011            let mut gen = Generator::new();
39012            gen.generate_expression(retention_period)?;
39013            s.push_str(&gen.output);
39014            parts.push(s);
39015        }
39016
39017        self.write_keyword("SYSTEM_VERSIONING");
39018        self.write("=");
39019
39020        if !parts.is_empty() {
39021            self.write_keyword("ON");
39022            self.write("(");
39023            self.write(&parts.join(", "));
39024            self.write(")");
39025        } else if e.on.is_some() {
39026            self.write_keyword("ON");
39027        } else {
39028            self.write_keyword("OFF");
39029        }
39030
39031        // Wrap in WITH(...) if with_ is set
39032        if e.with_.is_some() {
39033            let inner = self.output.clone();
39034            self.output.clear();
39035            self.write("WITH(");
39036            self.write(&inner);
39037            self.write(")");
39038        }
39039
39040        Ok(())
39041    }
39042
39043    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
39044        // Python: f"WITH ({self.expressions(expression, flat=True)})"
39045        self.write_keyword("WITH");
39046        self.write(" (");
39047        for (i, expr) in e.expressions.iter().enumerate() {
39048            if i > 0 {
39049                self.write(", ");
39050            }
39051            self.generate_expression(expr)?;
39052        }
39053        self.write(")");
39054        Ok(())
39055    }
39056
39057    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
39058        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
39059        // return self.func("XMLELEMENT", name, *expression.expressions)
39060        self.write_keyword("XMLELEMENT");
39061        self.write("(");
39062
39063        if e.evalname.is_some() {
39064            self.write_keyword("EVALNAME");
39065        } else {
39066            self.write_keyword("NAME");
39067        }
39068        self.write_space();
39069        self.generate_expression(&e.this)?;
39070
39071        for expr in &e.expressions {
39072            self.write(", ");
39073            self.generate_expression(expr)?;
39074        }
39075        self.write(")");
39076        Ok(())
39077    }
39078
39079    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
39080        // XMLGET(this, expression [, instance])
39081        self.write_keyword("XMLGET");
39082        self.write("(");
39083        self.generate_expression(&e.this)?;
39084        self.write(", ");
39085        self.generate_expression(&e.expression)?;
39086        if let Some(instance) = &e.instance {
39087            self.write(", ");
39088            self.generate_expression(instance)?;
39089        }
39090        self.write(")");
39091        Ok(())
39092    }
39093
39094    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
39095        // Python: this + optional (expr)
39096        self.generate_expression(&e.this)?;
39097        if let Some(expression) = &e.expression {
39098            self.write("(");
39099            self.generate_expression(expression)?;
39100            self.write(")");
39101        }
39102        Ok(())
39103    }
39104
39105    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
39106        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
39107        self.write_keyword("XMLTABLE");
39108        self.write("(");
39109
39110        if self.config.pretty {
39111            self.indent_level += 1;
39112            self.write_newline();
39113            self.write_indent();
39114            self.generate_expression(&e.this)?;
39115
39116            if let Some(passing) = &e.passing {
39117                self.write_newline();
39118                self.write_indent();
39119                self.write_keyword("PASSING");
39120                if let Expression::Tuple(tuple) = passing.as_ref() {
39121                    for expr in &tuple.expressions {
39122                        self.write_newline();
39123                        self.indent_level += 1;
39124                        self.write_indent();
39125                        self.generate_expression(expr)?;
39126                        self.indent_level -= 1;
39127                    }
39128                } else {
39129                    self.write_newline();
39130                    self.indent_level += 1;
39131                    self.write_indent();
39132                    self.generate_expression(passing)?;
39133                    self.indent_level -= 1;
39134                }
39135            }
39136
39137            if e.by_ref.is_some() {
39138                self.write_newline();
39139                self.write_indent();
39140                self.write_keyword("RETURNING SEQUENCE BY REF");
39141            }
39142
39143            if !e.columns.is_empty() {
39144                self.write_newline();
39145                self.write_indent();
39146                self.write_keyword("COLUMNS");
39147                for (i, col) in e.columns.iter().enumerate() {
39148                    self.write_newline();
39149                    self.indent_level += 1;
39150                    self.write_indent();
39151                    self.generate_expression(col)?;
39152                    self.indent_level -= 1;
39153                    if i < e.columns.len() - 1 {
39154                        self.write(",");
39155                    }
39156                }
39157            }
39158
39159            self.indent_level -= 1;
39160            self.write_newline();
39161            self.write_indent();
39162            self.write(")");
39163            return Ok(());
39164        }
39165
39166        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
39167        if let Some(namespaces) = &e.namespaces {
39168            self.write_keyword("XMLNAMESPACES");
39169            self.write("(");
39170            // Unwrap Tuple if present to avoid extra parentheses
39171            if let Expression::Tuple(tuple) = namespaces.as_ref() {
39172                for (i, expr) in tuple.expressions.iter().enumerate() {
39173                    if i > 0 {
39174                        self.write(", ");
39175                    }
39176                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
39177                    // See xmlnamespace_sql in generator.py
39178                    if !matches!(expr, Expression::Alias(_)) {
39179                        self.write_keyword("DEFAULT");
39180                        self.write_space();
39181                    }
39182                    self.generate_expression(expr)?;
39183                }
39184            } else {
39185                // Single namespace - check if DEFAULT
39186                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
39187                    self.write_keyword("DEFAULT");
39188                    self.write_space();
39189                }
39190                self.generate_expression(namespaces)?;
39191            }
39192            self.write("), ");
39193        }
39194
39195        // XPath expression
39196        self.generate_expression(&e.this)?;
39197
39198        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
39199        if let Some(passing) = &e.passing {
39200            self.write_space();
39201            self.write_keyword("PASSING");
39202            self.write_space();
39203            // Unwrap Tuple if present to avoid extra parentheses
39204            if let Expression::Tuple(tuple) = passing.as_ref() {
39205                for (i, expr) in tuple.expressions.iter().enumerate() {
39206                    if i > 0 {
39207                        self.write(", ");
39208                    }
39209                    self.generate_expression(expr)?;
39210                }
39211            } else {
39212                self.generate_expression(passing)?;
39213            }
39214        }
39215
39216        // RETURNING SEQUENCE BY REF
39217        if e.by_ref.is_some() {
39218            self.write_space();
39219            self.write_keyword("RETURNING SEQUENCE BY REF");
39220        }
39221
39222        // COLUMNS clause
39223        if !e.columns.is_empty() {
39224            self.write_space();
39225            self.write_keyword("COLUMNS");
39226            self.write_space();
39227            for (i, col) in e.columns.iter().enumerate() {
39228                if i > 0 {
39229                    self.write(", ");
39230                }
39231                self.generate_expression(col)?;
39232            }
39233        }
39234
39235        self.write(")");
39236        Ok(())
39237    }
39238
39239    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
39240        // Python: return self.connector_sql(expression, "XOR", stack)
39241        // Handles: this XOR expression or expressions joined by XOR
39242        if let Some(this) = &e.this {
39243            self.generate_expression(this)?;
39244            if let Some(expression) = &e.expression {
39245                self.write_space();
39246                self.write_keyword("XOR");
39247                self.write_space();
39248                self.generate_expression(expression)?;
39249            }
39250        }
39251
39252        // Handle multiple expressions
39253        for (i, expr) in e.expressions.iter().enumerate() {
39254            if i > 0 || e.this.is_some() {
39255                self.write_space();
39256                self.write_keyword("XOR");
39257                self.write_space();
39258            }
39259            self.generate_expression(expr)?;
39260        }
39261        Ok(())
39262    }
39263
39264    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
39265        // ZIPF(this, elementcount [, gen])
39266        self.write_keyword("ZIPF");
39267        self.write("(");
39268        self.generate_expression(&e.this)?;
39269        if let Some(elementcount) = &e.elementcount {
39270            self.write(", ");
39271            self.generate_expression(elementcount)?;
39272        }
39273        if let Some(gen) = &e.gen {
39274            self.write(", ");
39275            self.generate_expression(gen)?;
39276        }
39277        self.write(")");
39278        Ok(())
39279    }
39280}
39281
39282impl Default for Generator {
39283    fn default() -> Self {
39284        Self::new()
39285    }
39286}
39287
39288#[cfg(test)]
39289mod tests {
39290    use super::*;
39291    use crate::parser::Parser;
39292
39293    fn roundtrip(sql: &str) -> String {
39294        let ast = Parser::parse_sql(sql).unwrap();
39295        Generator::sql(&ast[0]).unwrap()
39296    }
39297
39298    #[test]
39299    fn test_simple_select() {
39300        let result = roundtrip("SELECT 1");
39301        assert_eq!(result, "SELECT 1");
39302    }
39303
39304    #[test]
39305    fn test_select_from() {
39306        let result = roundtrip("SELECT a, b FROM t");
39307        assert_eq!(result, "SELECT a, b FROM t");
39308    }
39309
39310    #[test]
39311    fn test_select_where() {
39312        let result = roundtrip("SELECT * FROM t WHERE x = 1");
39313        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
39314    }
39315
39316    #[test]
39317    fn test_select_join() {
39318        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
39319        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
39320    }
39321
39322    #[test]
39323    fn test_insert() {
39324        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
39325        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
39326    }
39327
39328    #[test]
39329    fn test_pretty_print() {
39330        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
39331        let result = Generator::pretty_sql(&ast[0]).unwrap();
39332        assert!(result.contains('\n'));
39333    }
39334
39335    #[test]
39336    fn test_window_function() {
39337        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
39338        assert_eq!(
39339            result,
39340            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
39341        );
39342    }
39343
39344    #[test]
39345    fn test_window_function_with_frame() {
39346        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
39347        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
39348    }
39349
39350    #[test]
39351    fn test_aggregate_with_filter() {
39352        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
39353        assert_eq!(
39354            result,
39355            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
39356        );
39357    }
39358
39359    #[test]
39360    fn test_subscript() {
39361        let result = roundtrip("SELECT arr[0]");
39362        assert_eq!(result, "SELECT arr[0]");
39363    }
39364
39365    // DDL tests
39366    #[test]
39367    fn test_create_table() {
39368        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
39369        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
39370    }
39371
39372    #[test]
39373    fn test_create_table_with_constraints() {
39374        let result = roundtrip(
39375            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
39376        );
39377        assert_eq!(
39378            result,
39379            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
39380        );
39381    }
39382
39383    #[test]
39384    fn test_create_table_if_not_exists() {
39385        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
39386        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
39387    }
39388
39389    #[test]
39390    fn test_drop_table() {
39391        let result = roundtrip("DROP TABLE users");
39392        assert_eq!(result, "DROP TABLE users");
39393    }
39394
39395    #[test]
39396    fn test_drop_table_if_exists_cascade() {
39397        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
39398        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
39399    }
39400
39401    #[test]
39402    fn test_alter_table_add_column() {
39403        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
39404        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
39405    }
39406
39407    #[test]
39408    fn test_alter_table_drop_column() {
39409        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
39410        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
39411    }
39412
39413    #[test]
39414    fn test_create_index() {
39415        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
39416        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
39417    }
39418
39419    #[test]
39420    fn test_create_unique_index() {
39421        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
39422        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
39423    }
39424
39425    #[test]
39426    fn test_drop_index() {
39427        let result = roundtrip("DROP INDEX idx_name");
39428        assert_eq!(result, "DROP INDEX idx_name");
39429
39430        let result = roundtrip(r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#);
39431        assert_eq!(
39432            result,
39433            r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#
39434        );
39435
39436        let result = roundtrip(r#"DROP INDEX "public"."IdxMixed""#);
39437        assert_eq!(result, r#"DROP INDEX "public"."IdxMixed""#);
39438    }
39439
39440    #[test]
39441    fn test_create_view() {
39442        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
39443        assert_eq!(
39444            result,
39445            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
39446        );
39447    }
39448
39449    #[test]
39450    fn test_drop_view() {
39451        let result = roundtrip("DROP VIEW active_users");
39452        assert_eq!(result, "DROP VIEW active_users");
39453    }
39454
39455    #[test]
39456    fn test_truncate() {
39457        let result = roundtrip("TRUNCATE TABLE users");
39458        assert_eq!(result, "TRUNCATE TABLE users");
39459    }
39460
39461    #[test]
39462    fn test_string_literal_escaping_default() {
39463        // Default: double single quotes
39464        let result = roundtrip("SELECT 'hello'");
39465        assert_eq!(result, "SELECT 'hello'");
39466
39467        // Single quotes are doubled
39468        let result = roundtrip("SELECT 'it''s a test'");
39469        assert_eq!(result, "SELECT 'it''s a test'");
39470    }
39471
39472    #[test]
39473    fn test_not_in_style_prefix_default_generic() {
39474        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
39475        assert_eq!(
39476            result,
39477            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
39478        );
39479    }
39480
39481    #[test]
39482    fn test_not_in_style_infix_generic_override() {
39483        let ast =
39484            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
39485                .unwrap();
39486        let config = GeneratorConfig {
39487            not_in_style: NotInStyle::Infix,
39488            ..Default::default()
39489        };
39490        let mut gen = Generator::with_config(config);
39491        let result = gen.generate(&ast[0]).unwrap();
39492        assert_eq!(
39493            result,
39494            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
39495        );
39496    }
39497
39498    #[test]
39499    fn test_string_literal_escaping_mysql() {
39500        use crate::dialects::DialectType;
39501
39502        let config = GeneratorConfig {
39503            dialect: Some(DialectType::MySQL),
39504            ..Default::default()
39505        };
39506
39507        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39508        let mut gen = Generator::with_config(config.clone());
39509        let result = gen.generate(&ast[0]).unwrap();
39510        assert_eq!(result, "SELECT 'hello'");
39511
39512        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
39513        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39514        let mut gen = Generator::with_config(config.clone());
39515        let result = gen.generate(&ast[0]).unwrap();
39516        assert_eq!(result, "SELECT 'it''s'");
39517    }
39518
39519    #[test]
39520    fn test_string_literal_escaping_postgres() {
39521        use crate::dialects::DialectType;
39522
39523        let config = GeneratorConfig {
39524            dialect: Some(DialectType::PostgreSQL),
39525            ..Default::default()
39526        };
39527
39528        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39529        let mut gen = Generator::with_config(config.clone());
39530        let result = gen.generate(&ast[0]).unwrap();
39531        assert_eq!(result, "SELECT 'hello'");
39532
39533        // PostgreSQL uses doubled quotes for regular strings
39534        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39535        let mut gen = Generator::with_config(config.clone());
39536        let result = gen.generate(&ast[0]).unwrap();
39537        assert_eq!(result, "SELECT 'it''s'");
39538    }
39539
39540    #[test]
39541    fn test_string_literal_escaping_bigquery() {
39542        use crate::dialects::DialectType;
39543
39544        let config = GeneratorConfig {
39545            dialect: Some(DialectType::BigQuery),
39546            ..Default::default()
39547        };
39548
39549        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39550        let mut gen = Generator::with_config(config.clone());
39551        let result = gen.generate(&ast[0]).unwrap();
39552        assert_eq!(result, "SELECT 'hello'");
39553
39554        // BigQuery escapes single quotes with backslash
39555        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39556        let mut gen = Generator::with_config(config.clone());
39557        let result = gen.generate(&ast[0]).unwrap();
39558        assert_eq!(result, "SELECT 'it\\'s'");
39559    }
39560
39561    #[test]
39562    fn test_generate_deep_and_chain_without_stack_growth() {
39563        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
39564            Expression::column("c0"),
39565            Expression::number(0),
39566        )));
39567
39568        for i in 1..2500 {
39569            let predicate = Expression::Eq(Box::new(BinaryOp::new(
39570                Expression::column(format!("c{i}")),
39571                Expression::number(i as i64),
39572            )));
39573            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
39574        }
39575
39576        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
39577        assert!(sql.contains("c2499 = 2499"), "{}", sql);
39578    }
39579
39580    #[test]
39581    fn test_generate_deep_or_chain_without_stack_growth() {
39582        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
39583            Expression::column("c0"),
39584            Expression::number(0),
39585        )));
39586
39587        for i in 1..2500 {
39588            let predicate = Expression::Eq(Box::new(BinaryOp::new(
39589                Expression::column(format!("c{i}")),
39590                Expression::number(i as i64),
39591            )));
39592            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
39593        }
39594
39595        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
39596        assert!(sql.contains("c2499 = 2499"), "{}", sql);
39597    }
39598}