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 more than 30 SQL dialects.
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#[derive(Debug, Clone, PartialEq, Eq)]
134enum TsqlDatePart {
135    Native(String),
136    Epoch,
137    IsoDayOfWeek,
138    Unsupported(String),
139}
140
141/// Identifier quote style (start/end characters)
142#[derive(Debug, Clone, Copy, PartialEq)]
143pub struct IdentifierQuoteStyle {
144    /// Start character for quoting identifiers (e.g., '"', '`', '[')
145    pub start: char,
146    /// End character for quoting identifiers (e.g., '"', '`', ']')
147    pub end: char,
148}
149
150impl Default for IdentifierQuoteStyle {
151    fn default() -> Self {
152        Self {
153            start: '"',
154            end: '"',
155        }
156    }
157}
158
159impl IdentifierQuoteStyle {
160    /// Double-quote style (PostgreSQL, Oracle, standard SQL)
161    pub const DOUBLE_QUOTE: Self = Self {
162        start: '"',
163        end: '"',
164    };
165    /// Backtick style (MySQL, BigQuery, Spark, Hive)
166    pub const BACKTICK: Self = Self {
167        start: '`',
168        end: '`',
169    };
170    /// Square bracket style (TSQL, SQLite)
171    pub const BRACKET: Self = Self {
172        start: '[',
173        end: ']',
174    };
175}
176
177/// Configuration for the SQL [`Generator`].
178///
179/// This is a comprehensive port of the Python sqlglot `Generator` class attributes.
180/// It controls every aspect of SQL output: formatting, quoting, dialect-specific
181/// syntax, feature support flags, and more.
182///
183/// Most users should start from `GeneratorConfig::default()` and override only the
184/// fields they need. Dialect-specific presets are applied automatically when
185/// `dialect` is set via the higher-level transpilation API.
186///
187/// # Key fields
188///
189/// | Field | Default | Purpose |
190/// |-------|---------|---------|
191/// | `dialect` | `None` | Target SQL dialect (e.g. PostgreSQL, MySQL, BigQuery) |
192/// | `pretty` | `false` | Enable multi-line, indented output |
193/// | `indent` | `"  "` | Indentation string used when `pretty` is true |
194/// | `max_text_width` | `80` | Soft line-width limit for pretty-printing |
195/// | `normalize_functions` | `Upper` | Function name casing (`Upper`, `Lower`, `None`) |
196/// | `identifier_quote_style` | `"…"` | Quote characters for identifiers |
197/// | `uppercase_keywords` | `true` | Whether SQL keywords are upper-cased |
198#[derive(Debug, Clone)]
199pub struct GeneratorConfig {
200    // ===== Basic formatting =====
201    /// Pretty print with indentation
202    pub pretty: bool,
203    /// Indentation string (default 2 spaces)
204    pub indent: &'static str,
205    /// Maximum text width before wrapping (default 80)
206    pub max_text_width: usize,
207    /// Quote identifier style (deprecated, use identifier_quote_style instead)
208    pub identifier_quote: char,
209    /// Identifier quote style with separate start/end characters
210    pub identifier_quote_style: IdentifierQuoteStyle,
211    /// Uppercase keywords
212    pub uppercase_keywords: bool,
213    /// Normalize identifiers to lowercase when generating
214    pub normalize_identifiers: bool,
215    /// Dialect type for dialect-specific generation
216    pub dialect: Option<crate::dialects::DialectType>,
217    /// Source dialect type (used during transpilation to distinguish identity vs cross-dialect)
218    pub source_dialect: Option<crate::dialects::DialectType>,
219    /// How unsupported generation should be handled.
220    pub unsupported_level: UnsupportedLevel,
221    /// Maximum number of unsupported diagnostics to include in raised errors.
222    pub max_unsupported: usize,
223    /// Complexity guard limits for recursive generation paths.
224    pub complexity_guard: ComplexityGuardOptions,
225    /// How to output function names (UPPER, lower, or as-is)
226    pub normalize_functions: NormalizeFunctions,
227    /// String escape character
228    pub string_escape: char,
229    /// Whether identifiers are case-sensitive
230    pub case_sensitive_identifiers: bool,
231    /// Whether unquoted identifiers can start with a digit
232    pub identifiers_can_start_with_digit: bool,
233    /// Whether to always quote identifiers regardless of reserved keyword status
234    /// Used by dialects like Athena/Presto that prefer quoted identifiers
235    pub always_quote_identifiers: bool,
236    /// How to render negated IN predicates in generic output.
237    pub not_in_style: NotInStyle,
238
239    // ===== Null handling =====
240    /// Whether null ordering (NULLS FIRST/LAST) is supported in ORDER BY
241    /// True: Full Support, false: No support
242    pub null_ordering_supported: bool,
243    /// Whether ignore nulls is inside the agg or outside
244    /// FIRST(x IGNORE NULLS) OVER vs FIRST(x) IGNORE NULLS OVER
245    pub ignore_nulls_in_func: bool,
246    /// Whether the NVL2 function is supported
247    pub nvl2_supported: bool,
248
249    // ===== Limit/Fetch =====
250    /// How to output LIMIT clauses
251    pub limit_fetch_style: LimitFetchStyle,
252    /// Whether to generate the limit as TOP <value> instead of LIMIT <value>
253    pub limit_is_top: bool,
254    /// Whether limit and fetch allows expressions or just literals
255    pub limit_only_literals: bool,
256
257    // ===== Interval =====
258    /// Whether INTERVAL uses single quoted string ('1 day' vs 1 DAY)
259    pub single_string_interval: bool,
260    /// Whether the plural form of date parts (e.g., "days") is supported in INTERVALs
261    pub interval_allows_plural_form: bool,
262
263    // ===== CTE =====
264    /// Whether WITH RECURSIVE keyword is required (vs just WITH for recursive CTEs)
265    pub cte_recursive_keyword_required: bool,
266
267    // ===== VALUES =====
268    /// Whether VALUES can be used as a table source
269    pub values_as_table: bool,
270    /// Wrap derived values in parens (standard but Spark doesn't support)
271    pub wrap_derived_values: bool,
272
273    // ===== TABLESAMPLE =====
274    /// Keyword for TABLESAMPLE seed: "SEED" or "REPEATABLE"
275    pub tablesample_seed_keyword: &'static str,
276    /// Whether parentheses are required around the table sample's expression
277    pub tablesample_requires_parens: bool,
278    /// Whether a table sample clause's size needs to be followed by ROWS keyword
279    pub tablesample_size_is_rows: bool,
280    /// The keyword(s) to use when generating a sample clause
281    pub tablesample_keywords: &'static str,
282    /// Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
283    pub tablesample_with_method: bool,
284    /// Whether the table alias comes after tablesample (Oracle, Hive)
285    pub alias_post_tablesample: bool,
286
287    // ===== Aggregate =====
288    /// Whether aggregate FILTER (WHERE ...) is supported
289    pub aggregate_filter_supported: bool,
290    /// Whether DISTINCT can be followed by multiple args in an AggFunc
291    pub multi_arg_distinct: bool,
292    /// Whether ANY/ALL quantifiers have no space before `(`: `ANY(` vs `ANY (`
293    pub quantified_no_paren_space: bool,
294    /// Whether MEDIAN(expr) is supported; if not, generates PERCENTILE_CONT
295    pub supports_median: bool,
296
297    // ===== SELECT =====
298    /// Whether SELECT ... INTO is supported
299    pub supports_select_into: bool,
300    /// Whether locking reads (SELECT ... FOR UPDATE/SHARE) are supported
301    pub locking_reads_supported: bool,
302
303    // ===== Table/Join =====
304    /// Whether a table is allowed to be renamed with a db
305    pub rename_table_with_db: bool,
306    /// Whether JOIN sides (LEFT, RIGHT) are supported with SEMI/ANTI join kinds
307    pub semi_anti_join_with_side: bool,
308    /// Whether named columns are allowed in table aliases
309    pub supports_table_alias_columns: bool,
310    /// Whether join hints should be generated
311    pub join_hints: bool,
312    /// Whether table hints should be generated
313    pub table_hints: bool,
314    /// Whether query hints should be generated
315    pub query_hints: bool,
316    /// What kind of separator to use for query hints
317    pub query_hint_sep: &'static str,
318    /// Whether Oracle-style (+) join markers are supported (Oracle, Exasol)
319    pub supports_column_join_marks: bool,
320
321    // ===== DDL =====
322    /// Whether CREATE INDEX USING method should have no space before column parens
323    /// true: `USING btree(col)`, false: `USING btree (col)`
324    pub index_using_no_space: bool,
325    /// Whether UNLOGGED tables can be created
326    pub supports_unlogged_tables: bool,
327    /// Whether CREATE TABLE LIKE statement is supported
328    pub supports_create_table_like: bool,
329    /// Whether the LikeProperty needs to be inside the schema clause
330    pub like_property_inside_schema: bool,
331    /// Whether the word COLUMN is included when adding a column with ALTER TABLE
332    pub alter_table_include_column_keyword: bool,
333    /// Whether CREATE TABLE .. COPY .. is supported (false = CLONE instead)
334    pub supports_table_copy: bool,
335    /// The syntax to use when altering the type of a column
336    pub alter_set_type: &'static str,
337    /// Whether to wrap <props> in AlterSet, e.g., ALTER ... SET (<props>)
338    pub alter_set_wrapped: bool,
339
340    // ===== Timestamp/Timezone =====
341    /// Whether TIMESTAMP WITH TIME ZONE is used (vs TIMESTAMPTZ)
342    pub tz_to_with_time_zone: bool,
343    /// Whether CONVERT_TIMEZONE() is supported
344    pub supports_convert_timezone: bool,
345
346    // ===== JSON =====
347    /// Whether the JSON extraction operators expect a value of type JSON
348    pub json_type_required_for_extraction: bool,
349    /// Whether bracketed keys like ["foo"] are supported in JSON paths
350    pub json_path_bracketed_key_supported: bool,
351    /// Whether to escape keys using single quotes in JSON paths
352    pub json_path_single_quote_escape: bool,
353    /// Whether to quote the generated expression of JsonPath
354    pub quote_json_path: bool,
355    /// What delimiter to use for separating JSON key/value pairs
356    pub json_key_value_pair_sep: &'static str,
357
358    // ===== COPY =====
359    /// Whether parameters from COPY statement are wrapped in parentheses
360    pub copy_params_are_wrapped: bool,
361    /// Whether values of params are set with "=" token or empty space
362    pub copy_params_eq_required: bool,
363    /// Whether COPY statement has INTO keyword
364    pub copy_has_into_keyword: bool,
365
366    // ===== Window functions =====
367    /// Whether EXCLUDE in window specification is supported
368    pub supports_window_exclude: bool,
369    /// UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
370    pub unnest_with_ordinality: bool,
371    /// Whether window frame keywords (ROWS/RANGE/GROUPS, PRECEDING/FOLLOWING) should be lowercase
372    /// Exasol uses lowercase for these specific keywords
373    pub lowercase_window_frame_keywords: bool,
374    /// Whether to normalize single-bound window frames to BETWEEN form
375    /// e.g., ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
376    pub normalize_window_frame_between: bool,
377
378    // ===== Array =====
379    /// Whether ARRAY_CONCAT can be generated with varlen args
380    pub array_concat_is_var_len: bool,
381    /// Whether exp.ArraySize should generate the dimension arg too
382    /// None -> Doesn't support, false -> optional, true -> required
383    pub array_size_dim_required: Option<bool>,
384    /// Whether any(f(x) for x in array) can be implemented
385    pub can_implement_array_any: bool,
386    /// Function used for array size
387    pub array_size_name: &'static str,
388
389    // ===== BETWEEN =====
390    /// Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN
391    pub supports_between_flags: bool,
392
393    // ===== Boolean =====
394    /// Whether comparing against booleans (e.g. x IS TRUE) is supported
395    pub is_bool_allowed: bool,
396    /// Whether conditions require booleans WHERE x = 0 vs WHERE x
397    pub ensure_bools: bool,
398
399    // ===== EXTRACT =====
400    /// Whether to generate an unquoted value for EXTRACT's date part argument
401    pub extract_allows_quotes: bool,
402    /// Whether to normalize date parts in EXTRACT
403    pub normalize_extract_date_parts: bool,
404
405    // ===== Other features =====
406    /// Whether the conditional TRY(expression) function is supported
407    pub try_supported: bool,
408    /// Whether the UESCAPE syntax in unicode strings is supported
409    pub supports_uescape: bool,
410    /// Whether the function TO_NUMBER is supported
411    pub supports_to_number: bool,
412    /// Whether CONCAT requires >1 arguments
413    pub supports_single_arg_concat: bool,
414    /// Whether LAST_DAY function supports a date part argument
415    pub last_day_supports_date_part: bool,
416    /// Whether a projection can explode into multiple rows
417    pub supports_exploding_projections: bool,
418    /// Whether UNIX_SECONDS(timestamp) is supported
419    pub supports_unix_seconds: bool,
420    /// Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
421    pub supports_like_quantifiers: bool,
422    /// Whether multi-argument DECODE(...) function is supported
423    pub supports_decode_case: bool,
424    /// Whether set op modifiers apply to the outer set op or select
425    pub set_op_modifiers: bool,
426    /// Whether FROM is supported in UPDATE statements
427    pub update_statement_supports_from: bool,
428
429    // ===== COLLATE =====
430    /// Whether COLLATE is a function instead of a binary operator
431    pub collate_is_func: bool,
432
433    // ===== INSERT =====
434    /// Whether to include "SET" keyword in "INSERT ... ON DUPLICATE KEY UPDATE"
435    pub duplicate_key_update_with_set: bool,
436    /// INSERT OVERWRITE TABLE x override
437    pub insert_overwrite: &'static str,
438
439    // ===== RETURNING =====
440    /// Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
441    pub returning_end: bool,
442
443    // ===== MERGE =====
444    /// Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
445    pub matched_by_source: bool,
446
447    // ===== CREATE FUNCTION =====
448    /// Whether create function uses an AS before the RETURN
449    pub create_function_return_as: bool,
450    /// Whether to use = instead of DEFAULT for parameter defaults (TSQL style)
451    pub parameter_default_equals: bool,
452
453    // ===== COMPUTED COLUMN =====
454    /// Whether to include the type of a computed column in the CREATE DDL
455    pub computed_column_with_type: bool,
456
457    // ===== UNPIVOT =====
458    /// Whether UNPIVOT aliases are Identifiers (false means they're Literals)
459    pub unpivot_aliases_are_identifiers: bool,
460
461    // ===== STAR =====
462    /// The keyword to use when generating a star projection with excluded columns
463    pub star_except: &'static str,
464
465    // ===== HEX =====
466    /// The HEX function name
467    pub hex_func: &'static str,
468
469    // ===== WITH =====
470    /// The keywords to use when prefixing WITH based properties
471    pub with_properties_prefix: &'static str,
472
473    // ===== PAD =====
474    /// Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional
475    pub pad_fill_pattern_is_required: bool,
476
477    // ===== INDEX =====
478    /// The string used for creating an index on a table
479    pub index_on: &'static str,
480
481    // ===== GROUPING =====
482    /// The separator for grouping sets and rollups
483    pub groupings_sep: &'static str,
484
485    // ===== STRUCT =====
486    /// Delimiters for STRUCT type
487    pub struct_delimiter: (&'static str, &'static str),
488    /// Whether Struct expressions use curly brace notation: {'key': value} (DuckDB)
489    pub struct_curly_brace_notation: bool,
490    /// Whether Array expressions omit the ARRAY keyword: [1, 2] instead of ARRAY[1, 2]
491    pub array_bracket_only: bool,
492    /// Separator between struct field name and type (": " for Hive, " " for others)
493    pub struct_field_sep: &'static str,
494
495    // ===== EXCEPT/INTERSECT =====
496    /// Whether EXCEPT and INTERSECT operations can return duplicates
497    pub except_intersect_support_all_clause: bool,
498
499    // ===== PARAMETERS/PLACEHOLDERS =====
500    /// Parameter token character (@ for TSQL, $ for PostgreSQL)
501    pub parameter_token: &'static str,
502    /// Named placeholder token (: for most, % for PostgreSQL)
503    pub named_placeholder_token: &'static str,
504
505    // ===== DATA TYPES =====
506    /// Whether data types support additional specifiers like CHAR or BYTE (oracle)
507    pub data_type_specifiers_allowed: bool,
508
509    // ===== COMMENT =====
510    /// Whether schema comments use `=` sign (COMMENT='value' vs COMMENT 'value')
511    /// StarRocks and Doris use naked COMMENT syntax without `=`
512    pub schema_comment_with_eq: bool,
513}
514
515impl Default for GeneratorConfig {
516    fn default() -> Self {
517        Self {
518            // ===== Basic formatting =====
519            pretty: false,
520            indent: "  ",
521            max_text_width: 80,
522            identifier_quote: '"',
523            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
524            uppercase_keywords: true,
525            normalize_identifiers: false,
526            dialect: None,
527            source_dialect: None,
528            unsupported_level: UnsupportedLevel::Warn,
529            max_unsupported: 3,
530            complexity_guard: ComplexityGuardOptions::default(),
531            normalize_functions: NormalizeFunctions::Upper,
532            string_escape: '\'',
533            case_sensitive_identifiers: false,
534            identifiers_can_start_with_digit: false,
535            always_quote_identifiers: false,
536            not_in_style: NotInStyle::Prefix,
537
538            // ===== Null handling =====
539            null_ordering_supported: true,
540            ignore_nulls_in_func: false,
541            nvl2_supported: true,
542
543            // ===== Limit/Fetch =====
544            limit_fetch_style: LimitFetchStyle::Limit,
545            limit_is_top: false,
546            limit_only_literals: false,
547
548            // ===== Interval =====
549            single_string_interval: false,
550            interval_allows_plural_form: true,
551
552            // ===== CTE =====
553            cte_recursive_keyword_required: true,
554
555            // ===== VALUES =====
556            values_as_table: true,
557            wrap_derived_values: true,
558
559            // ===== TABLESAMPLE =====
560            tablesample_seed_keyword: "SEED",
561            tablesample_requires_parens: true,
562            tablesample_size_is_rows: true,
563            tablesample_keywords: "TABLESAMPLE",
564            tablesample_with_method: true,
565            alias_post_tablesample: false,
566
567            // ===== Aggregate =====
568            aggregate_filter_supported: true,
569            multi_arg_distinct: true,
570            quantified_no_paren_space: false,
571            supports_median: true,
572
573            // ===== SELECT =====
574            supports_select_into: false,
575            locking_reads_supported: true,
576
577            // ===== Table/Join =====
578            rename_table_with_db: true,
579            semi_anti_join_with_side: true,
580            supports_table_alias_columns: true,
581            join_hints: true,
582            table_hints: true,
583            query_hints: true,
584            query_hint_sep: ", ",
585            supports_column_join_marks: false,
586
587            // ===== DDL =====
588            index_using_no_space: false,
589            supports_unlogged_tables: false,
590            supports_create_table_like: true,
591            like_property_inside_schema: false,
592            alter_table_include_column_keyword: true,
593            supports_table_copy: true,
594            alter_set_type: "SET DATA TYPE",
595            alter_set_wrapped: false,
596
597            // ===== Timestamp/Timezone =====
598            tz_to_with_time_zone: false,
599            supports_convert_timezone: false,
600
601            // ===== JSON =====
602            json_type_required_for_extraction: false,
603            json_path_bracketed_key_supported: true,
604            json_path_single_quote_escape: false,
605            quote_json_path: true,
606            json_key_value_pair_sep: ":",
607
608            // ===== COPY =====
609            copy_params_are_wrapped: true,
610            copy_params_eq_required: false,
611            copy_has_into_keyword: true,
612
613            // ===== Window functions =====
614            supports_window_exclude: false,
615            unnest_with_ordinality: true,
616            lowercase_window_frame_keywords: false,
617            normalize_window_frame_between: false,
618
619            // ===== Array =====
620            array_concat_is_var_len: true,
621            array_size_dim_required: None,
622            can_implement_array_any: false,
623            array_size_name: "ARRAY_LENGTH",
624
625            // ===== BETWEEN =====
626            supports_between_flags: false,
627
628            // ===== Boolean =====
629            is_bool_allowed: true,
630            ensure_bools: false,
631
632            // ===== EXTRACT =====
633            extract_allows_quotes: true,
634            normalize_extract_date_parts: false,
635
636            // ===== Other features =====
637            try_supported: true,
638            supports_uescape: true,
639            supports_to_number: true,
640            supports_single_arg_concat: true,
641            last_day_supports_date_part: true,
642            supports_exploding_projections: true,
643            supports_unix_seconds: false,
644            supports_like_quantifiers: true,
645            supports_decode_case: true,
646            set_op_modifiers: true,
647            update_statement_supports_from: true,
648
649            // ===== COLLATE =====
650            collate_is_func: false,
651
652            // ===== INSERT =====
653            duplicate_key_update_with_set: true,
654            insert_overwrite: " OVERWRITE TABLE",
655
656            // ===== RETURNING =====
657            returning_end: true,
658
659            // ===== MERGE =====
660            matched_by_source: true,
661
662            // ===== CREATE FUNCTION =====
663            create_function_return_as: true,
664            parameter_default_equals: false,
665
666            // ===== COMPUTED COLUMN =====
667            computed_column_with_type: true,
668
669            // ===== UNPIVOT =====
670            unpivot_aliases_are_identifiers: true,
671
672            // ===== STAR =====
673            star_except: "EXCEPT",
674
675            // ===== HEX =====
676            hex_func: "HEX",
677
678            // ===== WITH =====
679            with_properties_prefix: "WITH",
680
681            // ===== PAD =====
682            pad_fill_pattern_is_required: false,
683
684            // ===== INDEX =====
685            index_on: "ON",
686
687            // ===== GROUPING =====
688            groupings_sep: ",",
689
690            // ===== STRUCT =====
691            struct_delimiter: ("<", ">"),
692            struct_curly_brace_notation: false,
693            array_bracket_only: false,
694            struct_field_sep: " ",
695
696            // ===== EXCEPT/INTERSECT =====
697            except_intersect_support_all_clause: true,
698
699            // ===== PARAMETERS/PLACEHOLDERS =====
700            parameter_token: "@",
701            named_placeholder_token: ":",
702
703            // ===== DATA TYPES =====
704            data_type_specifiers_allowed: false,
705
706            // ===== COMMENT =====
707            schema_comment_with_eq: true,
708        }
709    }
710}
711
712/// SQL reserved keywords that require quoting when used as identifiers
713/// Based on ANSI SQL standards and common dialect-specific reserved words
714mod reserved_keywords {
715    use std::collections::HashSet;
716    use std::sync::LazyLock;
717
718    /// Standard SQL reserved keywords (ANSI SQL:2016)
719    pub static SQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
720        [
721            "all",
722            "alter",
723            "and",
724            "any",
725            "array",
726            "as",
727            "asc",
728            "at",
729            "authorization",
730            "begin",
731            "between",
732            "both",
733            "by",
734            "case",
735            "cast",
736            "check",
737            "collate",
738            "column",
739            "commit",
740            "constraint",
741            "create",
742            "cross",
743            "cube",
744            "current",
745            "current_date",
746            "current_time",
747            "current_timestamp",
748            "current_user",
749            "default",
750            "delete",
751            "desc",
752            "distinct",
753            "drop",
754            "else",
755            "end",
756            "escape",
757            "except",
758            "execute",
759            "exists",
760            "external",
761            "false",
762            "fetch",
763            "filter",
764            "for",
765            "foreign",
766            "from",
767            "full",
768            "function",
769            "grant",
770            "group",
771            "grouping",
772            "having",
773            "if",
774            "in",
775            "index",
776            "inner",
777            "insert",
778            "intersect",
779            "interval",
780            "into",
781            "is",
782            "join",
783            "key",
784            "leading",
785            "left",
786            "like",
787            "limit",
788            "local",
789            "localtime",
790            "localtimestamp",
791            "match",
792            "merge",
793            "natural",
794            "no",
795            "not",
796            "null",
797            "of",
798            "offset",
799            "on",
800            "only",
801            "or",
802            "order",
803            "outer",
804            "over",
805            "partition",
806            "primary",
807            "procedure",
808            "range",
809            "references",
810            "right",
811            "rollback",
812            "rollup",
813            "row",
814            "rows",
815            "select",
816            "session_user",
817            "set",
818            "some",
819            "table",
820            "tablesample",
821            "then",
822            "to",
823            "trailing",
824            "true",
825            "truncate",
826            "union",
827            "unique",
828            "unknown",
829            "update",
830            "user",
831            "using",
832            "values",
833            "view",
834            "when",
835            "where",
836            "window",
837            "with",
838        ]
839        .into_iter()
840        .collect()
841    });
842
843    /// BigQuery-specific reserved keywords
844    /// Based on: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
845    pub static BIGQUERY_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
846        let mut set = SQL_RESERVED.clone();
847        set.extend([
848            "assert_rows_modified",
849            "at",
850            "contains",
851            "cube",
852            "current",
853            "define",
854            "enum",
855            "escape",
856            "exclude",
857            "following",
858            "for",
859            "groups",
860            "hash",
861            "ignore",
862            "lateral",
863            "lookup",
864            "new",
865            "no",
866            "nulls",
867            "of",
868            "over",
869            "preceding",
870            "proto",
871            "qualify",
872            "recursive",
873            "respect",
874            "struct",
875            "tablesample",
876            "treat",
877            "unbounded",
878            "unnest",
879            "window",
880            "within",
881        ]);
882        // BigQuery does NOT reserve these keywords - they can be used as identifiers unquoted
883        set.remove("grant");
884        set.remove("key");
885        set.remove("index");
886        set.remove("offset");
887        set.remove("values");
888        set.remove("table");
889        set
890    });
891
892    /// MySQL-specific reserved keywords
893    pub static MYSQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
894        let mut set = SQL_RESERVED.clone();
895        set.extend([
896            "accessible",
897            "add",
898            "analyze",
899            "asensitive",
900            "before",
901            "bigint",
902            "binary",
903            "blob",
904            "call",
905            "cascade",
906            "change",
907            "char",
908            "character",
909            "condition",
910            "continue",
911            "convert",
912            "current_date",
913            "current_time",
914            "current_timestamp",
915            "current_user",
916            "cursor",
917            "database",
918            "databases",
919            "day_hour",
920            "day_microsecond",
921            "day_minute",
922            "day_second",
923            "dec",
924            "decimal",
925            "declare",
926            "delayed",
927            "describe",
928            "deterministic",
929            "distinctrow",
930            "div",
931            "double",
932            "dual",
933            "each",
934            "elseif",
935            "enclosed",
936            "escaped",
937            "exit",
938            "explain",
939            "float",
940            "float4",
941            "float8",
942            "force",
943            "get",
944            "high_priority",
945            "hour_microsecond",
946            "hour_minute",
947            "hour_second",
948            "ignore",
949            "infile",
950            "inout",
951            "insensitive",
952            "int",
953            "int1",
954            "int2",
955            "int3",
956            "int4",
957            "int8",
958            "integer",
959            "iterate",
960            "keys",
961            "kill",
962            "leave",
963            "linear",
964            "lines",
965            "load",
966            "lock",
967            "long",
968            "longblob",
969            "longtext",
970            "loop",
971            "low_priority",
972            "master_ssl_verify_server_cert",
973            "maxvalue",
974            "mediumblob",
975            "mediumint",
976            "mediumtext",
977            "middleint",
978            "minute_microsecond",
979            "minute_second",
980            "mod",
981            "modifies",
982            "no_write_to_binlog",
983            "numeric",
984            "optimize",
985            "option",
986            "optionally",
987            "out",
988            "outfile",
989            "precision",
990            "purge",
991            "read",
992            "reads",
993            "real",
994            "regexp",
995            "release",
996            "rename",
997            "repeat",
998            "replace",
999            "require",
1000            "resignal",
1001            "restrict",
1002            "return",
1003            "revoke",
1004            "rlike",
1005            "schema",
1006            "schemas",
1007            "second_microsecond",
1008            "sensitive",
1009            "separator",
1010            "show",
1011            "signal",
1012            "smallint",
1013            "spatial",
1014            "specific",
1015            "sql",
1016            "sql_big_result",
1017            "sql_calc_found_rows",
1018            "sql_small_result",
1019            "sqlexception",
1020            "sqlstate",
1021            "sqlwarning",
1022            "ssl",
1023            "starting",
1024            "straight_join",
1025            "terminated",
1026            "text",
1027            "tinyblob",
1028            "tinyint",
1029            "tinytext",
1030            "trigger",
1031            "undo",
1032            "unlock",
1033            "unsigned",
1034            "usage",
1035            "utc_date",
1036            "utc_time",
1037            "utc_timestamp",
1038            "varbinary",
1039            "varchar",
1040            "varcharacter",
1041            "varying",
1042            "while",
1043            "write",
1044            "xor",
1045            "year_month",
1046            "zerofill",
1047        ]);
1048        set.remove("table");
1049        set
1050    });
1051
1052    /// Doris-specific reserved keywords
1053    /// Extends MySQL reserved with additional Doris-specific words
1054    pub static DORIS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1055        let mut set = MYSQL_RESERVED.clone();
1056        set.extend([
1057            "aggregate",
1058            "anti",
1059            "array",
1060            "backend",
1061            "backup",
1062            "begin",
1063            "bitmap",
1064            "boolean",
1065            "broker",
1066            "buckets",
1067            "cached",
1068            "cancel",
1069            "cast",
1070            "catalog",
1071            "charset",
1072            "cluster",
1073            "collation",
1074            "columns",
1075            "comment",
1076            "commit",
1077            "config",
1078            "connection",
1079            "count",
1080            "current",
1081            "data",
1082            "date",
1083            "datetime",
1084            "day",
1085            "deferred",
1086            "distributed",
1087            "dynamic",
1088            "enable",
1089            "end",
1090            "events",
1091            "export",
1092            "external",
1093            "fields",
1094            "first",
1095            "follower",
1096            "format",
1097            "free",
1098            "frontend",
1099            "full",
1100            "functions",
1101            "global",
1102            "grants",
1103            "hash",
1104            "help",
1105            "hour",
1106            "install",
1107            "intermediate",
1108            "json",
1109            "label",
1110            "last",
1111            "less",
1112            "level",
1113            "link",
1114            "local",
1115            "location",
1116            "max",
1117            "merge",
1118            "min",
1119            "minute",
1120            "modify",
1121            "month",
1122            "name",
1123            "names",
1124            "negative",
1125            "nulls",
1126            "observer",
1127            "offset",
1128            "only",
1129            "open",
1130            "overwrite",
1131            "password",
1132            "path",
1133            "plan",
1134            "plugin",
1135            "plugins",
1136            "policy",
1137            "process",
1138            "properties",
1139            "property",
1140            "query",
1141            "quota",
1142            "recover",
1143            "refresh",
1144            "repair",
1145            "replica",
1146            "repository",
1147            "resource",
1148            "restore",
1149            "resume",
1150            "role",
1151            "roles",
1152            "rollback",
1153            "rollup",
1154            "routine",
1155            "sample",
1156            "second",
1157            "semi",
1158            "session",
1159            "signed",
1160            "snapshot",
1161            "start",
1162            "stats",
1163            "status",
1164            "stop",
1165            "stream",
1166            "string",
1167            "sum",
1168            "tables",
1169            "tablet",
1170            "temporary",
1171            "text",
1172            "timestamp",
1173            "transaction",
1174            "trash",
1175            "trim",
1176            "truncate",
1177            "type",
1178            "user",
1179            "value",
1180            "variables",
1181            "verbose",
1182            "version",
1183            "view",
1184            "warnings",
1185            "week",
1186            "work",
1187            "year",
1188        ]);
1189        set
1190    });
1191
1192    /// PostgreSQL-specific reserved keywords
1193    pub static POSTGRES_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1194        let mut set = SQL_RESERVED.clone();
1195        set.extend([
1196            "analyse",
1197            "analyze",
1198            "asymmetric",
1199            "binary",
1200            "collation",
1201            "concurrently",
1202            "current_catalog",
1203            "current_role",
1204            "current_schema",
1205            "deferrable",
1206            "do",
1207            "freeze",
1208            "ilike",
1209            "initially",
1210            "isnull",
1211            "lateral",
1212            "notnull",
1213            "placing",
1214            "returning",
1215            "similar",
1216            "symmetric",
1217            "variadic",
1218            "verbose",
1219        ]);
1220        // PostgreSQL doesn't require quoting for these keywords
1221        set.remove("default");
1222        set.remove("interval");
1223        set.remove("match");
1224        set.remove("offset");
1225        set.remove("table");
1226        set
1227    });
1228
1229    /// Redshift-specific reserved keywords
1230    /// Based on: https://docs.aws.amazon.com/redshift/latest/dg/r_pg_keywords.html
1231    /// Note: `index` is NOT reserved in Redshift (unlike PostgreSQL)
1232    pub static REDSHIFT_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1233        [
1234            "aes128",
1235            "aes256",
1236            "all",
1237            "allowoverwrite",
1238            "analyse",
1239            "analyze",
1240            "and",
1241            "any",
1242            "array",
1243            "as",
1244            "asc",
1245            "authorization",
1246            "az64",
1247            "backup",
1248            "between",
1249            "binary",
1250            "blanksasnull",
1251            "both",
1252            "bytedict",
1253            "bzip2",
1254            "case",
1255            "cast",
1256            "check",
1257            "collate",
1258            "column",
1259            "constraint",
1260            "create",
1261            "credentials",
1262            "cross",
1263            "current_date",
1264            "current_time",
1265            "current_timestamp",
1266            "current_user",
1267            "current_user_id",
1268            "default",
1269            "deferrable",
1270            "deflate",
1271            "defrag",
1272            "delta",
1273            "delta32k",
1274            "desc",
1275            "disable",
1276            "distinct",
1277            "do",
1278            "else",
1279            "emptyasnull",
1280            "enable",
1281            "encode",
1282            "encrypt",
1283            "encryption",
1284            "end",
1285            "except",
1286            "explicit",
1287            "false",
1288            "for",
1289            "foreign",
1290            "freeze",
1291            "from",
1292            "full",
1293            "globaldict256",
1294            "globaldict64k",
1295            "grant",
1296            "group",
1297            "gzip",
1298            "having",
1299            "identity",
1300            "ignore",
1301            "ilike",
1302            "in",
1303            "initially",
1304            "inner",
1305            "intersect",
1306            "interval",
1307            "into",
1308            "is",
1309            "isnull",
1310            "join",
1311            "leading",
1312            "left",
1313            "like",
1314            "limit",
1315            "localtime",
1316            "localtimestamp",
1317            "lun",
1318            "luns",
1319            "lzo",
1320            "lzop",
1321            "minus",
1322            "mostly16",
1323            "mostly32",
1324            "mostly8",
1325            "natural",
1326            "new",
1327            "not",
1328            "notnull",
1329            "null",
1330            "nulls",
1331            "off",
1332            "offline",
1333            "offset",
1334            "oid",
1335            "old",
1336            "on",
1337            "only",
1338            "open",
1339            "or",
1340            "order",
1341            "outer",
1342            "overlaps",
1343            "parallel",
1344            "partition",
1345            "percent",
1346            "permissions",
1347            "pivot",
1348            "placing",
1349            "primary",
1350            "raw",
1351            "readratio",
1352            "recover",
1353            "references",
1354            "rejectlog",
1355            "resort",
1356            "respect",
1357            "restore",
1358            "right",
1359            "select",
1360            "session_user",
1361            "similar",
1362            "snapshot",
1363            "some",
1364            "sysdate",
1365            "system",
1366            "table",
1367            "tag",
1368            "tdes",
1369            "text255",
1370            "text32k",
1371            "then",
1372            "timestamp",
1373            "to",
1374            "top",
1375            "trailing",
1376            "true",
1377            "truncatecolumns",
1378            "type",
1379            "union",
1380            "unique",
1381            "unnest",
1382            "unpivot",
1383            "user",
1384            "using",
1385            "verbose",
1386            "wallet",
1387            "when",
1388            "where",
1389            "with",
1390            "without",
1391        ]
1392        .into_iter()
1393        .collect()
1394    });
1395
1396    /// DuckDB-specific reserved keywords
1397    pub static DUCKDB_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1398        let mut set = POSTGRES_RESERVED.clone();
1399        set.extend([
1400            "anti",
1401            "asof",
1402            "columns",
1403            "describe",
1404            "groups",
1405            "macro",
1406            "pivot",
1407            "pivot_longer",
1408            "pivot_wider",
1409            "qualify",
1410            "replace",
1411            "respect",
1412            "semi",
1413            "show",
1414            "table",
1415            "unpivot",
1416        ]);
1417        set.remove("at");
1418        set.remove("key");
1419        set.remove("range");
1420        set.remove("row");
1421        set.remove("values");
1422        set
1423    });
1424
1425    /// Presto/Trino/Athena-specific reserved keywords
1426    pub static PRESTO_TRINO_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1427        let mut set = SQL_RESERVED.clone();
1428        set.extend([
1429            "alter",
1430            "and",
1431            "as",
1432            "between",
1433            "by",
1434            "case",
1435            "cast",
1436            "constraint",
1437            "create",
1438            "cross",
1439            "cube",
1440            "current_catalog",
1441            "current_date",
1442            "current_path",
1443            "current_role",
1444            "current_schema",
1445            "current_time",
1446            "current_timestamp",
1447            "current_user",
1448            "deallocate",
1449            "delete",
1450            "describe",
1451            "distinct",
1452            "drop",
1453            "else",
1454            "end",
1455            "escape",
1456            "except",
1457            "execute",
1458            "exists",
1459            "extract",
1460            "false",
1461            "for",
1462            "from",
1463            "full",
1464            "group",
1465            "grouping",
1466            "having",
1467            "in",
1468            "inner",
1469            "insert",
1470            "intersect",
1471            "into",
1472            "is",
1473            "join",
1474            "json_array",
1475            "json_exists",
1476            "json_object",
1477            "json_query",
1478            "json_table",
1479            "json_value",
1480            "left",
1481            "like",
1482            "listagg",
1483            "localtime",
1484            "localtimestamp",
1485            "natural",
1486            "normalize",
1487            "not",
1488            "null",
1489            "on",
1490            "or",
1491            "order",
1492            "outer",
1493            "prepare",
1494            "recursive",
1495            "right",
1496            "rollup",
1497            "select",
1498            "skip",
1499            "table",
1500            "then",
1501            "trim",
1502            "true",
1503            "uescape",
1504            "union",
1505            "unnest",
1506            "using",
1507            "values",
1508            "when",
1509            "where",
1510            "with",
1511        ]);
1512        // Match sqlglot behavior for Presto/Trino: KEY does not require identifier quoting.
1513        set.remove("key");
1514        set
1515    });
1516
1517    /// StarRocks-specific reserved keywords
1518    /// Based on: https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords
1519    pub static STARROCKS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1520        [
1521            "add",
1522            "all",
1523            "alter",
1524            "analyze",
1525            "and",
1526            "array",
1527            "as",
1528            "asc",
1529            "between",
1530            "bigint",
1531            "bitmap",
1532            "both",
1533            "by",
1534            "case",
1535            "char",
1536            "character",
1537            "check",
1538            "collate",
1539            "column",
1540            "compaction",
1541            "convert",
1542            "create",
1543            "cross",
1544            "cube",
1545            "current_date",
1546            "current_role",
1547            "current_time",
1548            "current_timestamp",
1549            "current_user",
1550            "database",
1551            "databases",
1552            "decimal",
1553            "decimalv2",
1554            "decimal32",
1555            "decimal64",
1556            "decimal128",
1557            "default",
1558            "deferred",
1559            "delete",
1560            "dense_rank",
1561            "desc",
1562            "describe",
1563            "distinct",
1564            "double",
1565            "drop",
1566            "dual",
1567            "else",
1568            "except",
1569            "exists",
1570            "explain",
1571            "false",
1572            "first_value",
1573            "float",
1574            "for",
1575            "force",
1576            "from",
1577            "full",
1578            "function",
1579            "grant",
1580            "group",
1581            "grouping",
1582            "grouping_id",
1583            "groups",
1584            "having",
1585            "hll",
1586            "host",
1587            "if",
1588            "ignore",
1589            "immediate",
1590            "in",
1591            "index",
1592            "infile",
1593            "inner",
1594            "insert",
1595            "int",
1596            "integer",
1597            "intersect",
1598            "into",
1599            "is",
1600            "join",
1601            "json",
1602            "key",
1603            "keys",
1604            "kill",
1605            "lag",
1606            "largeint",
1607            "last_value",
1608            "lateral",
1609            "lead",
1610            "left",
1611            "like",
1612            "limit",
1613            "load",
1614            "localtime",
1615            "localtimestamp",
1616            "maxvalue",
1617            "minus",
1618            "mod",
1619            "not",
1620            "ntile",
1621            "null",
1622            "on",
1623            "or",
1624            "order",
1625            "outer",
1626            "outfile",
1627            "over",
1628            "partition",
1629            "percentile",
1630            "primary",
1631            "procedure",
1632            "qualify",
1633            "range",
1634            "rank",
1635            "read",
1636            "regexp",
1637            "release",
1638            "rename",
1639            "replace",
1640            "revoke",
1641            "right",
1642            "rlike",
1643            "row",
1644            "row_number",
1645            "rows",
1646            "schema",
1647            "schemas",
1648            "select",
1649            "set",
1650            "set_var",
1651            "show",
1652            "smallint",
1653            "system",
1654            "table",
1655            "terminated",
1656            "text",
1657            "then",
1658            "tinyint",
1659            "to",
1660            "true",
1661            "union",
1662            "unique",
1663            "unsigned",
1664            "update",
1665            "use",
1666            "using",
1667            "values",
1668            "varchar",
1669            "when",
1670            "where",
1671            "with",
1672        ]
1673        .into_iter()
1674        .collect()
1675    });
1676
1677    /// SingleStore-specific reserved keywords
1678    /// Based on: https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/
1679    pub static SINGLESTORE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1680        let mut set = MYSQL_RESERVED.clone();
1681        set.extend([
1682            // Additional SingleStore reserved keywords from Python sqlglot
1683            // NOTE: "all" is excluded because ORDER BY ALL needs ALL unquoted
1684            "abs",
1685            "account",
1686            "acos",
1687            "adddate",
1688            "addtime",
1689            "admin",
1690            "aes_decrypt",
1691            "aes_encrypt",
1692            "aggregate",
1693            "aggregates",
1694            "aggregator",
1695            "anti_join",
1696            "any_value",
1697            "approx_count_distinct",
1698            "approx_percentile",
1699            "arrange",
1700            "arrangement",
1701            "asin",
1702            "atan",
1703            "atan2",
1704            "attach",
1705            "autostats",
1706            "avro",
1707            "background",
1708            "backup",
1709            "batch",
1710            "batches",
1711            "boot_strapping",
1712            "ceil",
1713            "ceiling",
1714            "coercibility",
1715            "columnar",
1716            "columnstore",
1717            "compile",
1718            "concurrent",
1719            "connection_id",
1720            "cos",
1721            "cot",
1722            "current_security_groups",
1723            "current_security_roles",
1724            "dayname",
1725            "dayofmonth",
1726            "dayofweek",
1727            "dayofyear",
1728            "degrees",
1729            "dot_product",
1730            "dump",
1731            "durability",
1732            "earliest",
1733            "echo",
1734            "election",
1735            "euclidean_distance",
1736            "exp",
1737            "extractor",
1738            "extractors",
1739            "floor",
1740            "foreground",
1741            "found_rows",
1742            "from_base64",
1743            "from_days",
1744            "from_unixtime",
1745            "fs",
1746            "fulltext",
1747            "gc",
1748            "gcs",
1749            "geography",
1750            "geography_area",
1751            "geography_contains",
1752            "geography_distance",
1753            "geography_intersects",
1754            "geography_latitude",
1755            "geography_length",
1756            "geography_longitude",
1757            "geographypoint",
1758            "geography_point",
1759            "geography_within_distance",
1760            "geometry",
1761            "geometry_area",
1762            "geometry_contains",
1763            "geometry_distance",
1764            "geometry_filter",
1765            "geometry_intersects",
1766            "geometry_length",
1767            "geometrypoint",
1768            "geometry_point",
1769            "geometry_within_distance",
1770            "geometry_x",
1771            "geometry_y",
1772            "greatest",
1773            "groups",
1774            "group_concat",
1775            "gzip",
1776            "hdfs",
1777            "hex",
1778            "highlight",
1779            "ifnull",
1780            "ilike",
1781            "inet_aton",
1782            "inet_ntoa",
1783            "inet6_aton",
1784            "inet6_ntoa",
1785            "initcap",
1786            "instr",
1787            "interpreter_mode",
1788            "isnull",
1789            "json",
1790            "json_agg",
1791            "json_array_contains_double",
1792            "json_array_contains_json",
1793            "json_array_contains_string",
1794            "json_delete_key",
1795            "json_extract_double",
1796            "json_extract_json",
1797            "json_extract_string",
1798            "json_extract_bigint",
1799            "json_get_type",
1800            "json_length",
1801            "json_set_double",
1802            "json_set_json",
1803            "json_set_string",
1804            "kafka",
1805            "lag",
1806            "last_day",
1807            "last_insert_id",
1808            "latest",
1809            "lcase",
1810            "lead",
1811            "leaf",
1812            "least",
1813            "leaves",
1814            "length",
1815            "license",
1816            "links",
1817            "llvm",
1818            "ln",
1819            "load",
1820            "locate",
1821            "log",
1822            "log10",
1823            "log2",
1824            "lpad",
1825            "lz4",
1826            "management",
1827            "match",
1828            "mbc",
1829            "md5",
1830            "median",
1831            "memsql",
1832            "memsql_deserialize",
1833            "memsql_serialize",
1834            "metadata",
1835            "microsecond",
1836            "minute",
1837            "model",
1838            "monthname",
1839            "months_between",
1840            "mpl",
1841            "namespace",
1842            "node",
1843            "noparam",
1844            "now",
1845            "nth_value",
1846            "ntile",
1847            "nullcols",
1848            "nullif",
1849            "object",
1850            "octet_length",
1851            "offsets",
1852            "online",
1853            "optimizer",
1854            "orphan",
1855            "parquet",
1856            "partitions",
1857            "pause",
1858            "percentile_cont",
1859            "percentile_disc",
1860            "periodic",
1861            "persisted",
1862            "pi",
1863            "pipeline",
1864            "pipelines",
1865            "plancache",
1866            "plugins",
1867            "pool",
1868            "pools",
1869            "pow",
1870            "power",
1871            "process",
1872            "processlist",
1873            "profile",
1874            "profiles",
1875            "quarter",
1876            "queries",
1877            "query",
1878            "radians",
1879            "rand",
1880            "record",
1881            "reduce",
1882            "redundancy",
1883            "regexp_match",
1884            "regexp_substr",
1885            "remote",
1886            "replication",
1887            "resource",
1888            "resource_pool",
1889            "restore",
1890            "retry",
1891            "role",
1892            "roles",
1893            "round",
1894            "rpad",
1895            "rtrim",
1896            "running",
1897            "s3",
1898            "scalar",
1899            "sec_to_time",
1900            "second",
1901            "security_lists_intersect",
1902            "semi_join",
1903            "sha",
1904            "sha1",
1905            "sha2",
1906            "shard",
1907            "sharded",
1908            "sharded_id",
1909            "sigmoid",
1910            "sign",
1911            "sin",
1912            "skip",
1913            "sleep",
1914            "snapshot",
1915            "soname",
1916            "sparse",
1917            "spatial_check_index",
1918            "split",
1919            "sqrt",
1920            "standalone",
1921            "std",
1922            "stddev",
1923            "stddev_pop",
1924            "stddev_samp",
1925            "stop",
1926            "str_to_date",
1927            "subdate",
1928            "substr",
1929            "substring_index",
1930            "success",
1931            "synchronize",
1932            "table_checksum",
1933            "tan",
1934            "task",
1935            "timediff",
1936            "time_bucket",
1937            "time_format",
1938            "time_to_sec",
1939            "timestampadd",
1940            "timestampdiff",
1941            "to_base64",
1942            "to_char",
1943            "to_date",
1944            "to_days",
1945            "to_json",
1946            "to_number",
1947            "to_seconds",
1948            "to_timestamp",
1949            "tracelogs",
1950            "transform",
1951            "trim",
1952            "trunc",
1953            "truncate",
1954            "ucase",
1955            "unhex",
1956            "unix_timestamp",
1957            "utc_date",
1958            "utc_time",
1959            "utc_timestamp",
1960            "vacuum",
1961            "variance",
1962            "var_pop",
1963            "var_samp",
1964            "vector_sub",
1965            "voting",
1966            "week",
1967            "weekday",
1968            "weekofyear",
1969            "workload",
1970            "year",
1971        ]);
1972        // Remove "all" because ORDER BY ALL needs ALL unquoted
1973        set.remove("all");
1974        set
1975    });
1976
1977    /// SQLite-specific reserved keywords
1978    /// SQLite has a very minimal set of reserved keywords - most things can be used as identifiers unquoted
1979    /// Reference: https://www.sqlite.org/lang_keywords.html
1980    pub static SQLITE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1981        // SQLite only truly reserves these - everything else can be used as identifier unquoted
1982        [
1983            "abort",
1984            "action",
1985            "add",
1986            "after",
1987            "all",
1988            "alter",
1989            "always",
1990            "analyze",
1991            "and",
1992            "as",
1993            "asc",
1994            "attach",
1995            "autoincrement",
1996            "before",
1997            "begin",
1998            "between",
1999            "by",
2000            "cascade",
2001            "case",
2002            "cast",
2003            "check",
2004            "collate",
2005            "column",
2006            "commit",
2007            "conflict",
2008            "constraint",
2009            "create",
2010            "cross",
2011            "current",
2012            "current_date",
2013            "current_time",
2014            "current_timestamp",
2015            "database",
2016            "default",
2017            "deferrable",
2018            "deferred",
2019            "delete",
2020            "desc",
2021            "detach",
2022            "distinct",
2023            "do",
2024            "drop",
2025            "each",
2026            "else",
2027            "end",
2028            "escape",
2029            "except",
2030            "exclude",
2031            "exclusive",
2032            "exists",
2033            "explain",
2034            "fail",
2035            "filter",
2036            "first",
2037            "following",
2038            "for",
2039            "foreign",
2040            "from",
2041            "full",
2042            "generated",
2043            "glob",
2044            "group",
2045            "groups",
2046            "having",
2047            "if",
2048            "ignore",
2049            "immediate",
2050            "in",
2051            "index",
2052            "indexed",
2053            "initially",
2054            "inner",
2055            "insert",
2056            "instead",
2057            "intersect",
2058            "into",
2059            "is",
2060            "isnull",
2061            "join",
2062            "key",
2063            "last",
2064            "left",
2065            "like",
2066            "limit",
2067            "natural",
2068            "no",
2069            "not",
2070            "nothing",
2071            "notnull",
2072            "null",
2073            "nulls",
2074            "of",
2075            "offset",
2076            "on",
2077            "or",
2078            "order",
2079            "others",
2080            "outer",
2081            "partition",
2082            "plan",
2083            "pragma",
2084            "preceding",
2085            "primary",
2086            "query",
2087            "raise",
2088            "range",
2089            "recursive",
2090            "references",
2091            "regexp",
2092            "reindex",
2093            "release",
2094            "rename",
2095            "replace",
2096            "restrict",
2097            "returning",
2098            "right",
2099            "rollback",
2100            "row",
2101            "rows",
2102            "savepoint",
2103            "select",
2104            "set",
2105            "table",
2106            "temp",
2107            "temporary",
2108            "then",
2109            "ties",
2110            "to",
2111            "transaction",
2112            "trigger",
2113            "unbounded",
2114            "union",
2115            "unique",
2116            "update",
2117            "using",
2118            "vacuum",
2119            "values",
2120            "view",
2121            "virtual",
2122            "when",
2123            "where",
2124            "window",
2125            "with",
2126            "without",
2127        ]
2128        .into_iter()
2129        .collect()
2130    });
2131}
2132
2133impl Generator {
2134    /// Create a new generator with the default configuration.
2135    ///
2136    /// Equivalent to `Generator::with_config(GeneratorConfig::default())`.
2137    /// Uses uppercase keywords, double-quote identifier quoting, no pretty-printing,
2138    /// and no dialect-specific transformations.
2139    pub fn new() -> Self {
2140        Self::with_config(GeneratorConfig::default())
2141    }
2142
2143    /// Create a generator with a custom [`GeneratorConfig`].
2144    ///
2145    /// Use this when you need dialect-specific output, pretty-printing, or other
2146    /// non-default settings.
2147    pub fn with_config(config: GeneratorConfig) -> Self {
2148        Self::with_arc_config(Arc::new(config))
2149    }
2150
2151    /// Create a generator from a shared [`Arc<GeneratorConfig>`].
2152    ///
2153    /// This avoids cloning the configuration when multiple generators share the
2154    /// same settings (e.g. during transpilation). The [`Arc`] is cheap to clone.
2155    pub(crate) fn with_arc_config(config: Arc<GeneratorConfig>) -> Self {
2156        Self {
2157            config,
2158            output: String::new(),
2159            unsupported_messages: Vec::new(),
2160            indent_level: 0,
2161            athena_hive_context: false,
2162            sqlite_inline_pk_columns: std::collections::HashSet::new(),
2163            merge_strip_qualifiers: Vec::new(),
2164            clickhouse_nullable_depth: 0,
2165        }
2166    }
2167
2168    /// Add column aliases to a query expression for TSQL SELECT INTO.
2169    /// This ensures that unaliased columns get explicit aliases (e.g., `a` -> `a AS a`).
2170    /// Recursively processes all SELECT expressions in the query tree.
2171    fn add_column_aliases_to_query(expr: Expression) -> Expression {
2172        match expr {
2173            Expression::Select(mut select) => {
2174                // Add aliases to all select expressions that don't already have them
2175                select.expressions = select
2176                    .expressions
2177                    .into_iter()
2178                    .map(|e| Self::add_alias_to_expression(e))
2179                    .collect();
2180
2181                // Recursively process subqueries in FROM clause
2182                if let Some(ref mut from) = select.from {
2183                    from.expressions = from
2184                        .expressions
2185                        .iter()
2186                        .cloned()
2187                        .map(|e| Self::add_column_aliases_to_query(e))
2188                        .collect();
2189                }
2190
2191                Expression::Select(select)
2192            }
2193            Expression::Subquery(mut sq) => {
2194                sq.this = Self::add_column_aliases_to_query(sq.this);
2195                Expression::Subquery(sq)
2196            }
2197            Expression::Paren(mut p) => {
2198                p.this = Self::add_column_aliases_to_query(p.this);
2199                Expression::Paren(p)
2200            }
2201            // For other expressions (Union, Intersect, etc.), pass through
2202            other => other,
2203        }
2204    }
2205
2206    /// Add an alias to a single select expression if it doesn't already have one.
2207    /// Returns the expression with alias (e.g., `a` -> `a AS a`).
2208    fn add_alias_to_expression(expr: Expression) -> Expression {
2209        use crate::expressions::Alias;
2210
2211        match &expr {
2212            // Already aliased - just return it
2213            Expression::Alias(_) => expr,
2214
2215            // Column reference: add alias from column name
2216            Expression::Column(col) => Expression::Alias(Box::new(Alias {
2217                this: expr.clone(),
2218                alias: col.name.clone(),
2219                column_aliases: Vec::new(),
2220                alias_explicit_as: false,
2221                alias_keyword: None,
2222                pre_alias_comments: Vec::new(),
2223                trailing_comments: Vec::new(),
2224                inferred_type: None,
2225            })),
2226
2227            // Identifier: add alias from identifier name
2228            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
2229                this: expr.clone(),
2230                alias: ident.clone(),
2231                column_aliases: Vec::new(),
2232                alias_explicit_as: false,
2233                alias_keyword: None,
2234                pre_alias_comments: Vec::new(),
2235                trailing_comments: Vec::new(),
2236                inferred_type: None,
2237            })),
2238
2239            // Subquery: recursively process and add alias if inner returns a named column
2240            Expression::Subquery(sq) => {
2241                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
2242                // Subqueries that are already aliased keep their alias
2243                if sq.alias.is_some() {
2244                    processed
2245                } else {
2246                    // If there's no alias, keep it as-is (let TSQL handle it)
2247                    processed
2248                }
2249            }
2250
2251            // Star expressions (*) - don't alias
2252            Expression::Star(_) => expr,
2253
2254            // For other expressions, don't add an alias
2255            // (function calls, literals, etc. would need explicit aliases anyway)
2256            _ => expr,
2257        }
2258    }
2259
2260    /// Try to evaluate a constant arithmetic expression to a number literal.
2261    /// Returns the evaluated result if the expression is a constant arithmetic expression,
2262    /// otherwise returns the original expression.
2263    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
2264        match expr {
2265            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
2266                let Literal::Number(n) = lit.as_ref() else {
2267                    unreachable!()
2268                };
2269                n.parse::<i64>().ok()
2270            }
2271            Expression::Add(op) => {
2272                let left = Self::try_evaluate_constant(&op.left)?;
2273                let right = Self::try_evaluate_constant(&op.right)?;
2274                Some(left + right)
2275            }
2276            Expression::Sub(op) => {
2277                let left = Self::try_evaluate_constant(&op.left)?;
2278                let right = Self::try_evaluate_constant(&op.right)?;
2279                Some(left - right)
2280            }
2281            Expression::Mul(op) => {
2282                let left = Self::try_evaluate_constant(&op.left)?;
2283                let right = Self::try_evaluate_constant(&op.right)?;
2284                Some(left * right)
2285            }
2286            Expression::Div(op) => {
2287                let left = Self::try_evaluate_constant(&op.left)?;
2288                let right = Self::try_evaluate_constant(&op.right)?;
2289                if right != 0 {
2290                    Some(left / right)
2291                } else {
2292                    None
2293                }
2294            }
2295            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2296            _ => None,
2297        }
2298    }
2299
2300    /// Check if an identifier is a reserved keyword for the current dialect
2301    fn is_reserved_keyword(&self, name: &str) -> bool {
2302        use crate::dialects::DialectType;
2303        let mut buf = [0u8; 128];
2304        let lower_ref: &str = if name.len() <= 128 {
2305            for (i, b) in name.bytes().enumerate() {
2306                buf[i] = b.to_ascii_lowercase();
2307            }
2308            // SAFETY: input is valid UTF-8 and ASCII lowercase preserves that
2309            std::str::from_utf8(&buf[..name.len()]).unwrap_or(name)
2310        } else {
2311            return false;
2312        };
2313
2314        match self.config.dialect {
2315            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2316            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2317                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2318            }
2319            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2320            Some(DialectType::SingleStore) => {
2321                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2322            }
2323            Some(DialectType::StarRocks) => {
2324                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2325            }
2326            Some(DialectType::PostgreSQL)
2327            | Some(DialectType::CockroachDB)
2328            | Some(DialectType::Materialize)
2329            | Some(DialectType::RisingWave) => {
2330                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2331            }
2332            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2333            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2334            // meaning it never quotes identifiers based on reserved word status.
2335            Some(DialectType::Snowflake) => false,
2336            // ClickHouse: don't quote reserved keywords to preserve identity output
2337            Some(DialectType::ClickHouse) => false,
2338            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2339            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2340            Some(DialectType::Teradata) => false,
2341            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2342            Some(DialectType::TSQL)
2343            | Some(DialectType::Fabric)
2344            | Some(DialectType::Oracle)
2345            | Some(DialectType::Spark)
2346            | Some(DialectType::Databricks)
2347            | Some(DialectType::Hive)
2348            | Some(DialectType::Solr) => false,
2349            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2350                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2351            }
2352            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2353            // For Generic dialect or None, don't add extra quoting to preserve identity
2354            Some(DialectType::Generic) | None => false,
2355            // For other dialects, use standard SQL reserved keywords
2356            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2357        }
2358    }
2359
2360    /// Normalize function name based on dialect settings
2361    fn normalize_func_name<'a>(&self, name: &'a str) -> Cow<'a, str> {
2362        match self.config.normalize_functions {
2363            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
2364            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
2365            NormalizeFunctions::None => Cow::Borrowed(name),
2366        }
2367    }
2368
2369    /// Generate a SQL string from an AST expression.
2370    ///
2371    /// This is the primary generation method. It clears any previous internal state,
2372    /// walks the expression tree, and returns the resulting SQL text. The output
2373    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2374    ///
2375    /// The generator can be reused across multiple calls; each call to `generate`
2376    /// resets the internal buffer.
2377    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2378        self.output.clear();
2379        self.unsupported_messages.clear();
2380        enforce_generate_ast(expr, &self.config.complexity_guard)?;
2381        self.generate_expression(expr)?;
2382        if self.config.unsupported_level == UnsupportedLevel::Raise
2383            && !self.unsupported_messages.is_empty()
2384        {
2385            return Err(crate::error::Error::generate(
2386                self.format_unsupported_messages(),
2387            ));
2388        }
2389        Ok(std::mem::take(&mut self.output))
2390    }
2391
2392    /// Returns the unsupported diagnostics collected during the most recent generate call.
2393    pub fn unsupported_messages(&self) -> &[String] {
2394        &self.unsupported_messages
2395    }
2396
2397    fn unsupported(&mut self, message: impl Into<String>) -> Result<()> {
2398        let message = message.into();
2399        if self.config.unsupported_level == UnsupportedLevel::Immediate {
2400            return Err(crate::error::Error::generate(message));
2401        }
2402        self.unsupported_messages.push(message);
2403        Ok(())
2404    }
2405
2406    fn write_unsupported_comment(&mut self, message: &str) -> Result<()> {
2407        self.unsupported(message.to_string())?;
2408        self.write("/* ");
2409        self.write(message);
2410        self.write(" */");
2411        Ok(())
2412    }
2413
2414    fn format_unsupported_messages(&self) -> String {
2415        let limit = self.config.max_unsupported.max(1);
2416        if self.unsupported_messages.len() <= limit {
2417            return self.unsupported_messages.join("; ");
2418        }
2419
2420        let mut messages = self
2421            .unsupported_messages
2422            .iter()
2423            .take(limit)
2424            .cloned()
2425            .collect::<Vec<_>>();
2426        messages.push(format!(
2427            "... and {} more",
2428            self.unsupported_messages.len() - limit
2429        ));
2430        messages.join("; ")
2431    }
2432
2433    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2434    ///
2435    /// This is a static helper that creates a throwaway `Generator` internally.
2436    /// For repeated generation, prefer constructing a `Generator` once and calling
2437    /// [`generate`](Self::generate) on it.
2438    pub fn sql(expr: &Expression) -> Result<String> {
2439        let mut gen = Generator::new();
2440        gen.generate(expr)
2441    }
2442
2443    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2444    ///
2445    /// Produces human-readable output with newlines and indentation. A trailing
2446    /// semicolon is appended automatically if not already present.
2447    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2448        let config = GeneratorConfig {
2449            pretty: true,
2450            ..Default::default()
2451        };
2452        let mut gen = Generator::with_config(config);
2453        let mut sql = gen.generate(expr)?;
2454        // Add semicolon for pretty output
2455        if !sql.ends_with(';') {
2456            sql.push(';');
2457        }
2458        Ok(sql)
2459    }
2460
2461    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2462        #[cfg(feature = "stacker")]
2463        {
2464            let red_zone = if cfg!(debug_assertions) {
2465                4 * 1024 * 1024
2466            } else {
2467                1024 * 1024
2468            };
2469            stacker::maybe_grow(red_zone, 8 * 1024 * 1024, || {
2470                self.generate_expression_inner(expr)
2471            })
2472        }
2473        #[cfg(not(feature = "stacker"))]
2474        {
2475            self.generate_expression_inner(expr)
2476        }
2477    }
2478
2479    fn generate_expression_inner(&mut self, expr: &Expression) -> Result<()> {
2480        match expr {
2481            Expression::Select(select) => self.generate_select(select),
2482            Expression::Union(union) => self.generate_union(union),
2483            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2484            Expression::Except(except) => self.generate_except(except),
2485            Expression::Insert(insert) => self.generate_insert(insert),
2486            Expression::Update(update) => self.generate_update(update),
2487            Expression::Delete(delete) => self.generate_delete(delete),
2488            Expression::Literal(lit) => self.generate_literal(lit),
2489            Expression::Boolean(b) => self.generate_boolean(b),
2490            Expression::Null(_) => {
2491                self.write_keyword("NULL");
2492                Ok(())
2493            }
2494            Expression::Identifier(id) => self.generate_identifier(id),
2495            Expression::Column(col) => self.generate_column(col),
2496            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2497            Expression::Connect(c) => self.generate_connect_expr(c),
2498            Expression::Prior(p) => self.generate_prior(p),
2499            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2500            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2501            Expression::Table(table) => self.generate_table(table),
2502            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2503            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2504            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2505            Expression::Star(star) => self.generate_star(star),
2506            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2507            Expression::Alias(alias) => self.generate_alias(alias),
2508            Expression::Cast(cast) => self.generate_cast(cast),
2509            Expression::Collation(coll) => self.generate_collation(coll),
2510            Expression::Case(case) => self.generate_case(case),
2511            Expression::Function(func) => self.generate_function(func),
2512            Expression::FunctionEmits(fe) => self.generate_function_emits(fe),
2513            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2514            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2515            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2516            Expression::Interval(interval) => self.generate_interval(interval),
2517
2518            // String functions
2519            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2520            Expression::Substring(f) => self.generate_substring(f),
2521            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2522            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2523            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
2524            Expression::Trim(f) => self.generate_trim(f),
2525            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2526            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2527            Expression::Replace(f) => self.generate_replace(f),
2528            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2529            Expression::Left(f) => self.generate_left_right("LEFT", f),
2530            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2531            Expression::Repeat(f) => self.generate_repeat(f),
2532            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2533            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2534            Expression::Split(f) => self.generate_split(f),
2535            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2536            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2537            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2538            Expression::Overlay(f) => self.generate_overlay(f),
2539
2540            // Math functions
2541            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2542            Expression::Round(f) => self.generate_round(f),
2543            Expression::Floor(f) => self.generate_floor(f),
2544            Expression::Ceil(f) => self.generate_ceil(f),
2545            Expression::Power(f) => self.generate_power(f),
2546            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2547            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2548            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2549            Expression::Log(f) => self.generate_log(f),
2550            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2551            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2552            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2553            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2554
2555            // Date/time functions
2556            Expression::CurrentDate(_) => {
2557                self.write_keyword("CURRENT_DATE");
2558                Ok(())
2559            }
2560            Expression::CurrentTime(f) => self.generate_current_time(f),
2561            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2562            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2563            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2564            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2565            Expression::DateDiff(f) => self.generate_datediff(f),
2566            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2567            Expression::Extract(f) => self.generate_extract(f),
2568            Expression::ToDate(f) => self.generate_to_date(f),
2569            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2570
2571            // Control flow functions
2572            Expression::Coalesce(f) => {
2573                // Use original function name if preserved (COALESCE, IFNULL)
2574                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2575                self.generate_vararg_func(func_name, &f.expressions)
2576            }
2577            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2578            Expression::IfFunc(f) => self.generate_if_func(f),
2579            Expression::IfNull(f) => self.generate_ifnull(f),
2580            Expression::Nvl(f) => self.generate_nvl(f),
2581            Expression::Nvl2(f) => self.generate_nvl2(f),
2582
2583            // Type conversion
2584            Expression::TryCast(cast) => self.generate_try_cast(cast),
2585            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2586
2587            // Typed aggregate functions
2588            Expression::Count(f) => self.generate_count(f),
2589            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2590            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2591            Expression::Min(f) => self.generate_agg_func("MIN", f),
2592            Expression::Max(f) => self.generate_agg_func("MAX", f),
2593            Expression::GroupConcat(f) => self.generate_group_concat(f),
2594            Expression::StringAgg(f) => self.generate_string_agg(f),
2595            Expression::ListAgg(f) => self.generate_listagg(f),
2596            Expression::ArrayAgg(f) => {
2597                // Allow cross-dialect transforms to override the function name
2598                // (e.g., COLLECT_LIST for Spark)
2599                let override_name = f
2600                    .name
2601                    .as_ref()
2602                    .filter(|n| !n.eq_ignore_ascii_case("ARRAY_AGG"))
2603                    .map(|n| n.to_ascii_uppercase());
2604                match override_name {
2605                    Some(name) => self.generate_agg_func(&name, f),
2606                    None => self.generate_agg_func("ARRAY_AGG", f),
2607                }
2608            }
2609            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2610            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2611            Expression::SumIf(f) => self.generate_sum_if(f),
2612            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2613            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2614            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2615            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2616            Expression::VarPop(f) => {
2617                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2618                    "VARIANCE_POP"
2619                } else {
2620                    "VAR_POP"
2621                };
2622                self.generate_agg_func(name, f)
2623            }
2624            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2625            Expression::Skewness(f) => {
2626                let name = match self.config.dialect {
2627                    Some(DialectType::Snowflake) => "SKEW",
2628                    _ => "SKEWNESS",
2629                };
2630                self.generate_agg_func(name, f)
2631            }
2632            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2633            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2634            Expression::First(f) => self.generate_agg_func_with_ignore_nulls_bool("FIRST", f),
2635            Expression::Last(f) => self.generate_agg_func_with_ignore_nulls_bool("LAST", f),
2636            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2637            Expression::ApproxDistinct(f) => {
2638                match self.config.dialect {
2639                    Some(DialectType::Hive)
2640                    | Some(DialectType::Spark)
2641                    | Some(DialectType::Databricks)
2642                    | Some(DialectType::BigQuery) => {
2643                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2644                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2645                    }
2646                    Some(DialectType::Redshift) => {
2647                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2648                        self.write_keyword("APPROXIMATE COUNT");
2649                        self.write("(");
2650                        self.write_keyword("DISTINCT");
2651                        self.write(" ");
2652                        self.generate_expression(&f.this)?;
2653                        self.write(")");
2654                        Ok(())
2655                    }
2656                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2657                }
2658            }
2659            Expression::ApproxCountDistinct(f) => {
2660                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2661            }
2662            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2663            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2664            Expression::LogicalAnd(f) => {
2665                let name = match self.config.dialect {
2666                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2667                    Some(DialectType::Spark)
2668                    | Some(DialectType::Databricks)
2669                    | Some(DialectType::PostgreSQL)
2670                    | Some(DialectType::DuckDB)
2671                    | Some(DialectType::Redshift) => "BOOL_AND",
2672                    Some(DialectType::Oracle)
2673                    | Some(DialectType::SQLite)
2674                    | Some(DialectType::MySQL) => "MIN",
2675                    _ => "BOOL_AND",
2676                };
2677                self.generate_agg_func(name, f)
2678            }
2679            Expression::LogicalOr(f) => {
2680                let name = match self.config.dialect {
2681                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2682                    Some(DialectType::Spark)
2683                    | Some(DialectType::Databricks)
2684                    | Some(DialectType::PostgreSQL)
2685                    | Some(DialectType::DuckDB)
2686                    | Some(DialectType::Redshift) => "BOOL_OR",
2687                    Some(DialectType::Oracle)
2688                    | Some(DialectType::SQLite)
2689                    | Some(DialectType::MySQL) => "MAX",
2690                    _ => "BOOL_OR",
2691                };
2692                self.generate_agg_func(name, f)
2693            }
2694
2695            // Typed window functions
2696            Expression::RowNumber(_) => {
2697                if self.config.dialect == Some(DialectType::ClickHouse) {
2698                    self.write("row_number");
2699                } else {
2700                    self.write_keyword("ROW_NUMBER");
2701                }
2702                self.write("()");
2703                Ok(())
2704            }
2705            Expression::Rank(r) => {
2706                self.write_keyword("RANK");
2707                self.write("(");
2708                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2709                if !r.args.is_empty() {
2710                    for (i, arg) in r.args.iter().enumerate() {
2711                        if i > 0 {
2712                            self.write(", ");
2713                        }
2714                        self.generate_expression(arg)?;
2715                    }
2716                } else if let Some(order_by) = &r.order_by {
2717                    // DuckDB: RANK(ORDER BY col)
2718                    self.write_keyword(" ORDER BY ");
2719                    for (i, ob) in order_by.iter().enumerate() {
2720                        if i > 0 {
2721                            self.write(", ");
2722                        }
2723                        self.generate_ordered(ob)?;
2724                    }
2725                }
2726                self.write(")");
2727                Ok(())
2728            }
2729            Expression::DenseRank(dr) => {
2730                self.write_keyword("DENSE_RANK");
2731                self.write("(");
2732                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2733                for (i, arg) in dr.args.iter().enumerate() {
2734                    if i > 0 {
2735                        self.write(", ");
2736                    }
2737                    self.generate_expression(arg)?;
2738                }
2739                self.write(")");
2740                Ok(())
2741            }
2742            Expression::NTile(f) => self.generate_ntile(f),
2743            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2744            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2745            Expression::FirstValue(f) => {
2746                self.generate_value_func_with_ignore_nulls_bool("FIRST_VALUE", f)
2747            }
2748            Expression::LastValue(f) => {
2749                self.generate_value_func_with_ignore_nulls_bool("LAST_VALUE", f)
2750            }
2751            Expression::NthValue(f) => self.generate_nth_value(f),
2752            Expression::PercentRank(pr) => {
2753                self.write_keyword("PERCENT_RANK");
2754                self.write("(");
2755                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2756                if !pr.args.is_empty() {
2757                    for (i, arg) in pr.args.iter().enumerate() {
2758                        if i > 0 {
2759                            self.write(", ");
2760                        }
2761                        self.generate_expression(arg)?;
2762                    }
2763                } else if let Some(order_by) = &pr.order_by {
2764                    // DuckDB: PERCENT_RANK(ORDER BY col)
2765                    self.write_keyword(" ORDER BY ");
2766                    for (i, ob) in order_by.iter().enumerate() {
2767                        if i > 0 {
2768                            self.write(", ");
2769                        }
2770                        self.generate_ordered(ob)?;
2771                    }
2772                }
2773                self.write(")");
2774                Ok(())
2775            }
2776            Expression::CumeDist(cd) => {
2777                self.write_keyword("CUME_DIST");
2778                self.write("(");
2779                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2780                if !cd.args.is_empty() {
2781                    for (i, arg) in cd.args.iter().enumerate() {
2782                        if i > 0 {
2783                            self.write(", ");
2784                        }
2785                        self.generate_expression(arg)?;
2786                    }
2787                } else if let Some(order_by) = &cd.order_by {
2788                    // DuckDB: CUME_DIST(ORDER BY col)
2789                    self.write_keyword(" ORDER BY ");
2790                    for (i, ob) in order_by.iter().enumerate() {
2791                        if i > 0 {
2792                            self.write(", ");
2793                        }
2794                        self.generate_ordered(ob)?;
2795                    }
2796                }
2797                self.write(")");
2798                Ok(())
2799            }
2800            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2801            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2802
2803            // Additional string functions
2804            Expression::Contains(f) => {
2805                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2806            }
2807            Expression::StartsWith(f) => {
2808                let name = match self.config.dialect {
2809                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2810                    _ => "STARTS_WITH",
2811                };
2812                self.generate_binary_func(name, &f.this, &f.expression)
2813            }
2814            Expression::EndsWith(f) => {
2815                let name = match self.config.dialect {
2816                    Some(DialectType::Snowflake) => "ENDSWITH",
2817                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2818                    Some(DialectType::ClickHouse) => "endsWith",
2819                    _ => "ENDS_WITH",
2820                };
2821                self.generate_binary_func(name, &f.this, &f.expression)
2822            }
2823            Expression::Position(f) => self.generate_position(f),
2824            Expression::Initcap(f) => match self.config.dialect {
2825                Some(DialectType::Presto)
2826                | Some(DialectType::Trino)
2827                | Some(DialectType::Athena) => {
2828                    self.write_keyword("REGEXP_REPLACE");
2829                    self.write("(");
2830                    self.generate_expression(&f.this)?;
2831                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2832                    Ok(())
2833                }
2834                _ => self.generate_simple_func("INITCAP", &f.this),
2835            },
2836            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2837            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2838            Expression::CharFunc(f) => self.generate_char_func(f),
2839            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2840            Expression::Levenshtein(f) => {
2841                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2842            }
2843
2844            // Additional math functions
2845            Expression::ModFunc(f) => self.generate_mod_func(f),
2846            Expression::Random(_) => {
2847                self.write_keyword("RANDOM");
2848                self.write("()");
2849                Ok(())
2850            }
2851            Expression::Rand(f) => self.generate_rand(f),
2852            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2853            Expression::Pi(_) => {
2854                self.write_keyword("PI");
2855                self.write("()");
2856                Ok(())
2857            }
2858            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2859            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2860            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2861            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2862            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2863            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2864            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2865            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2866            Expression::Atan2(f) => {
2867                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2868                self.generate_binary_func(name, &f.this, &f.expression)
2869            }
2870
2871            // Control flow
2872            Expression::Decode(f) => self.generate_decode(f),
2873
2874            // Additional date/time functions
2875            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2876            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2877            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2878            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2879            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2880            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2881            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2882            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2883            Expression::DayOfWeek(f) => {
2884                let name = match self.config.dialect {
2885                    Some(DialectType::Presto)
2886                    | Some(DialectType::Trino)
2887                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2888                    Some(DialectType::DuckDB) => "ISODOW",
2889                    _ => "DAYOFWEEK",
2890                };
2891                self.generate_simple_func(name, &f.this)
2892            }
2893            Expression::DayOfMonth(f) => {
2894                let name = match self.config.dialect {
2895                    Some(DialectType::Presto)
2896                    | Some(DialectType::Trino)
2897                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2898                    _ => "DAYOFMONTH",
2899                };
2900                self.generate_simple_func(name, &f.this)
2901            }
2902            Expression::DayOfYear(f) => {
2903                let name = match self.config.dialect {
2904                    Some(DialectType::Presto)
2905                    | Some(DialectType::Trino)
2906                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2907                    _ => "DAYOFYEAR",
2908                };
2909                self.generate_simple_func(name, &f.this)
2910            }
2911            Expression::WeekOfYear(f) => {
2912                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2913                let name = match self.config.dialect {
2914                    Some(DialectType::Hive)
2915                    | Some(DialectType::DuckDB)
2916                    | Some(DialectType::Spark)
2917                    | Some(DialectType::Databricks)
2918                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2919                    _ => "WEEK_OF_YEAR",
2920                };
2921                self.generate_simple_func(name, &f.this)
2922            }
2923            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2924            Expression::AddMonths(f) => {
2925                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2926            }
2927            Expression::MonthsBetween(f) => {
2928                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2929            }
2930            Expression::LastDay(f) => self.generate_last_day(f),
2931            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2932            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2933            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2934            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2935            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2936            Expression::MakeDate(f) => self.generate_make_date(f),
2937            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2938            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2939
2940            // Array functions
2941            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2942            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2943            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2944            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2945            Expression::ArrayContains(f) => {
2946                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2947            }
2948            Expression::ArrayPosition(f) => {
2949                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2950            }
2951            Expression::ArrayAppend(f) => {
2952                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2953            }
2954            Expression::ArrayPrepend(f) => {
2955                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2956            }
2957            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2958            Expression::ArraySort(f) => self.generate_array_sort(f),
2959            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2960            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2961            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2962            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2963            Expression::Unnest(f) => self.generate_unnest(f),
2964            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2965            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2966            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2967            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2968            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2969            Expression::ArrayCompact(f) => {
2970                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2971                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2972                    self.write("LIST_FILTER(");
2973                    self.generate_expression(&f.this)?;
2974                    self.write(", _u -> NOT _u IS NULL)");
2975                    Ok(())
2976                } else {
2977                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2978                }
2979            }
2980            Expression::ArrayIntersect(f) => {
2981                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2982                self.generate_vararg_func(func_name, &f.expressions)
2983            }
2984            Expression::ArrayUnion(f) => {
2985                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2986            }
2987            Expression::ArrayExcept(f) => {
2988                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2989            }
2990            Expression::ArrayRemove(f) => {
2991                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2992            }
2993            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
2994            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
2995            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
2996
2997            // Struct functions
2998            Expression::StructFunc(f) => self.generate_struct_constructor(f),
2999            Expression::StructExtract(f) => self.generate_struct_extract(f),
3000            Expression::NamedStruct(f) => self.generate_named_struct(f),
3001
3002            // Map functions
3003            Expression::MapFunc(f) => self.generate_map_constructor(f),
3004            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
3005            Expression::MapFromArrays(f) => {
3006                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
3007            }
3008            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
3009            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
3010            Expression::MapContainsKey(f) => {
3011                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
3012            }
3013            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
3014            Expression::ElementAt(f) => {
3015                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
3016            }
3017            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
3018            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
3019
3020            // JSON functions
3021            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
3022            Expression::JsonExtractScalar(f) => {
3023                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
3024            }
3025            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
3026            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
3027            Expression::JsonObject(f) => self.generate_json_object(f),
3028            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
3029            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
3030            Expression::JsonArrayLength(f) => {
3031                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
3032            }
3033            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
3034            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
3035            Expression::ParseJson(f) => {
3036                let name = match self.config.dialect {
3037                    Some(DialectType::Presto)
3038                    | Some(DialectType::Trino)
3039                    | Some(DialectType::Athena) => "JSON_PARSE",
3040                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
3041                        // PostgreSQL: CAST(x AS JSON)
3042                        self.write_keyword("CAST");
3043                        self.write("(");
3044                        self.generate_expression(&f.this)?;
3045                        self.write_keyword(" AS ");
3046                        self.write_keyword("JSON");
3047                        self.write(")");
3048                        return Ok(());
3049                    }
3050                    Some(DialectType::Hive)
3051                    | Some(DialectType::Spark)
3052                    | Some(DialectType::MySQL)
3053                    | Some(DialectType::SingleStore)
3054                    | Some(DialectType::TiDB)
3055                    | Some(DialectType::TSQL) => {
3056                        // Hive/Spark/MySQL/TSQL: just emit the string literal
3057                        self.generate_expression(&f.this)?;
3058                        return Ok(());
3059                    }
3060                    Some(DialectType::DuckDB) => "JSON",
3061                    _ => "PARSE_JSON",
3062                };
3063                self.generate_simple_func(name, &f.this)
3064            }
3065            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
3066            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
3067            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
3068            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
3069            Expression::JsonMergePatch(f) => {
3070                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
3071            }
3072            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
3073            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
3074
3075            // Type casting/conversion
3076            Expression::Convert(f) => self.generate_convert(f),
3077            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
3078
3079            // Additional expressions
3080            Expression::Lambda(f) => self.generate_lambda(f),
3081            Expression::Parameter(f) => self.generate_parameter(f),
3082            Expression::Placeholder(f) => self.generate_placeholder(f),
3083            Expression::NamedArgument(f) => self.generate_named_argument(f),
3084            Expression::TableArgument(f) => self.generate_table_argument(f),
3085            Expression::SqlComment(f) => self.generate_sql_comment(f),
3086
3087            // Additional predicates
3088            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
3089            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
3090            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
3091            Expression::SimilarTo(f) => self.generate_similar_to(f),
3092            Expression::Any(f) => self.generate_quantified("ANY", f),
3093            Expression::All(f) => self.generate_quantified("ALL", f),
3094            Expression::Overlaps(f) => self.generate_overlaps(f),
3095
3096            // Bitwise operations
3097            Expression::BitwiseLeftShift(op) => {
3098                if matches!(
3099                    self.config.dialect,
3100                    Some(DialectType::Presto) | Some(DialectType::Trino)
3101                ) {
3102                    self.write_keyword("BITWISE_LEFT_SHIFT");
3103                    self.write("(");
3104                    self.generate_expression(&op.left)?;
3105                    self.write(", ");
3106                    self.generate_expression(&op.right)?;
3107                    self.write(")");
3108                    Ok(())
3109                } else if matches!(
3110                    self.config.dialect,
3111                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3112                ) {
3113                    self.write_keyword("SHIFTLEFT");
3114                    self.write("(");
3115                    self.generate_expression(&op.left)?;
3116                    self.write(", ");
3117                    self.generate_expression(&op.right)?;
3118                    self.write(")");
3119                    Ok(())
3120                } else {
3121                    self.generate_binary_op(op, "<<")
3122                }
3123            }
3124            Expression::BitwiseRightShift(op) => {
3125                if matches!(
3126                    self.config.dialect,
3127                    Some(DialectType::Presto) | Some(DialectType::Trino)
3128                ) {
3129                    self.write_keyword("BITWISE_RIGHT_SHIFT");
3130                    self.write("(");
3131                    self.generate_expression(&op.left)?;
3132                    self.write(", ");
3133                    self.generate_expression(&op.right)?;
3134                    self.write(")");
3135                    Ok(())
3136                } else if matches!(
3137                    self.config.dialect,
3138                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3139                ) {
3140                    self.write_keyword("SHIFTRIGHT");
3141                    self.write("(");
3142                    self.generate_expression(&op.left)?;
3143                    self.write(", ");
3144                    self.generate_expression(&op.right)?;
3145                    self.write(")");
3146                    Ok(())
3147                } else {
3148                    self.generate_binary_op(op, ">>")
3149                }
3150            }
3151            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3152            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3153            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3154
3155            // Array/struct/map access
3156            Expression::Subscript(s) => self.generate_subscript(s),
3157            Expression::Dot(d) => self.generate_dot_access(d),
3158            Expression::MethodCall(m) => self.generate_method_call(m),
3159            Expression::ArraySlice(s) => self.generate_array_slice(s),
3160
3161            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3162            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3163            Expression::Add(op) => self.generate_binary_op(op, "+"),
3164            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3165            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3166            Expression::Div(op) => self.generate_binary_op(op, "/"),
3167            Expression::IntDiv(f) => {
3168                use crate::dialects::DialectType;
3169                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3170                    // DuckDB uses // operator for integer division
3171                    self.generate_expression(&f.this)?;
3172                    self.write(" // ");
3173                    self.generate_expression(&f.expression)?;
3174                    Ok(())
3175                } else if matches!(
3176                    self.config.dialect,
3177                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3178                ) {
3179                    // Hive/Spark use DIV as an infix operator
3180                    self.generate_expression(&f.this)?;
3181                    self.write(" ");
3182                    self.write_keyword("DIV");
3183                    self.write(" ");
3184                    self.generate_expression(&f.expression)?;
3185                    Ok(())
3186                } else {
3187                    // Other dialects use DIV function
3188                    self.write_keyword("DIV");
3189                    self.write("(");
3190                    self.generate_expression(&f.this)?;
3191                    self.write(", ");
3192                    self.generate_expression(&f.expression)?;
3193                    self.write(")");
3194                    Ok(())
3195                }
3196            }
3197            Expression::Mod(op) => {
3198                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3199                    self.generate_binary_op(op, "MOD")
3200                } else {
3201                    self.generate_binary_op(op, "%")
3202                }
3203            }
3204            Expression::Eq(op) => self.generate_binary_op(op, "="),
3205            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3206            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3207            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3208            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3209            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3210            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3211            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3212            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3213            Expression::Concat(op) => {
3214                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3215                if self.config.dialect == Some(DialectType::Solr) {
3216                    self.generate_binary_op(op, "OR")
3217                } else if self.config.dialect == Some(DialectType::MySQL) {
3218                    self.generate_mysql_concat_from_concat(op)
3219                } else {
3220                    self.generate_binary_op(op, "||")
3221                }
3222            }
3223            Expression::BitwiseAnd(op) => {
3224                // Presto/Trino use BITWISE_AND function
3225                if matches!(
3226                    self.config.dialect,
3227                    Some(DialectType::Presto) | Some(DialectType::Trino)
3228                ) {
3229                    self.write_keyword("BITWISE_AND");
3230                    self.write("(");
3231                    self.generate_expression(&op.left)?;
3232                    self.write(", ");
3233                    self.generate_expression(&op.right)?;
3234                    self.write(")");
3235                    Ok(())
3236                } else {
3237                    self.generate_binary_op(op, "&")
3238                }
3239            }
3240            Expression::BitwiseOr(op) => {
3241                // Presto/Trino use BITWISE_OR function
3242                if matches!(
3243                    self.config.dialect,
3244                    Some(DialectType::Presto) | Some(DialectType::Trino)
3245                ) {
3246                    self.write_keyword("BITWISE_OR");
3247                    self.write("(");
3248                    self.generate_expression(&op.left)?;
3249                    self.write(", ");
3250                    self.generate_expression(&op.right)?;
3251                    self.write(")");
3252                    Ok(())
3253                } else {
3254                    self.generate_binary_op(op, "|")
3255                }
3256            }
3257            Expression::BitwiseXor(op) => {
3258                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3259                if matches!(
3260                    self.config.dialect,
3261                    Some(DialectType::Presto) | Some(DialectType::Trino)
3262                ) {
3263                    self.write_keyword("BITWISE_XOR");
3264                    self.write("(");
3265                    self.generate_expression(&op.left)?;
3266                    self.write(", ");
3267                    self.generate_expression(&op.right)?;
3268                    self.write(")");
3269                    Ok(())
3270                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3271                    self.generate_binary_op(op, "#")
3272                } else {
3273                    self.generate_binary_op(op, "^")
3274                }
3275            }
3276            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3277            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3278            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3279            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3280            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3281            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3282            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3283            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3284            Expression::JSONBContains(f) => {
3285                // PostgreSQL JSONB contains key/path operators: a ? b, a @? b
3286                self.generate_expression(&f.this)?;
3287                self.write_space();
3288                self.write(f.original_name.as_deref().unwrap_or("?"));
3289                self.write_space();
3290                self.generate_expression(&f.expression)
3291            }
3292            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3293            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3294            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3295            Expression::Not(op) => match &op.this {
3296                Expression::Like(like) => self.generate_like_op_negated(like, "LIKE"),
3297                Expression::ILike(like) => self.generate_like_op_negated(like, "ILIKE"),
3298                _ => self.generate_unary_op(op, "NOT"),
3299            },
3300            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3301            Expression::BitwiseNot(op) => {
3302                // Presto/Trino use BITWISE_NOT function
3303                if matches!(
3304                    self.config.dialect,
3305                    Some(DialectType::Presto) | Some(DialectType::Trino)
3306                ) {
3307                    self.write_keyword("BITWISE_NOT");
3308                    self.write("(");
3309                    self.generate_expression(&op.this)?;
3310                    self.write(")");
3311                    Ok(())
3312                } else {
3313                    self.generate_unary_op(op, "~")
3314                }
3315            }
3316            Expression::In(in_expr) => self.generate_in(in_expr),
3317            Expression::Between(between) => self.generate_between(between),
3318            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3319            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3320            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3321            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3322            Expression::Is(is_expr) => self.generate_is(is_expr),
3323            Expression::Exists(exists) => self.generate_exists(exists),
3324            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3325            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3326            Expression::Paren(paren) => {
3327                // JoinedTable already outputs its own parentheses, so don't double-wrap
3328                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3329
3330                if !skip_parens {
3331                    self.write("(");
3332                    if self.config.pretty {
3333                        self.write_newline();
3334                        self.indent_level += 1;
3335                        self.write_indent();
3336                    }
3337                }
3338                self.generate_expression(&paren.this)?;
3339                if !skip_parens {
3340                    if self.config.pretty {
3341                        self.write_newline();
3342                        self.indent_level -= 1;
3343                        self.write_indent();
3344                    }
3345                    self.write(")");
3346                }
3347                // Output trailing comments after closing paren
3348                for comment in &paren.trailing_comments {
3349                    self.write(" ");
3350                    self.write_formatted_comment(comment);
3351                }
3352                Ok(())
3353            }
3354            Expression::Array(arr) => self.generate_array(arr),
3355            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3356            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3357            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3358            Expression::DataType(dt) => self.generate_data_type(dt),
3359            Expression::Raw(raw) => {
3360                self.write(&raw.sql);
3361                Ok(())
3362            }
3363            Expression::CreateTask(task) => self.generate_create_task(task),
3364            Expression::TryCatch(try_catch) => self.generate_try_catch(try_catch),
3365            Expression::Command(cmd) => {
3366                self.write(&cmd.this);
3367                if matches!(self.config.dialect, Some(DialectType::ClickHouse))
3368                    && cmd
3369                        .this
3370                        .trim_start()
3371                        .get(..7)
3372                        .is_some_and(|prefix| prefix.eq_ignore_ascii_case("EXPLAIN"))
3373                {
3374                    for _ in 0..Self::missing_closing_parens_outside_quotes(&cmd.this) {
3375                        self.write(")");
3376                    }
3377                }
3378                Ok(())
3379            }
3380            Expression::Kill(kill) => {
3381                self.write_keyword("KILL");
3382                if let Some(kind) = &kill.kind {
3383                    self.write_space();
3384                    self.write_keyword(kind);
3385                }
3386                self.write_space();
3387                self.generate_expression(&kill.this)?;
3388                Ok(())
3389            }
3390            Expression::Prepare(prepare) => self.generate_prepare(prepare),
3391            Expression::Execute(exec) => {
3392                self.write_keyword("EXECUTE");
3393                self.write_space();
3394                self.generate_expression(&exec.this)?;
3395                if exec.prepared {
3396                    if !exec.arguments.is_empty() {
3397                        self.write("(");
3398                        for (i, argument) in exec.arguments.iter().enumerate() {
3399                            if i > 0 {
3400                                self.write(", ");
3401                            }
3402                            self.generate_expression(argument)?;
3403                        }
3404                        self.write(")");
3405                    }
3406                    return Ok(());
3407                }
3408                for (i, param) in exec.parameters.iter().enumerate() {
3409                    if i == 0 {
3410                        self.write_space();
3411                    } else {
3412                        self.write(", ");
3413                    }
3414                    self.write(&param.name);
3415                    // Only write = value for named parameters (not positional)
3416                    if !param.positional {
3417                        self.write(" = ");
3418                        self.generate_expression(&param.value)?;
3419                    }
3420                    if param.output {
3421                        self.write_space();
3422                        self.write_keyword("OUTPUT");
3423                    }
3424                }
3425                if let Some(ref suffix) = exec.suffix {
3426                    self.write_space();
3427                    self.write(suffix);
3428                }
3429                Ok(())
3430            }
3431            Expression::Annotated(annotated) => {
3432                self.generate_expression(&annotated.this)?;
3433                for comment in &annotated.trailing_comments {
3434                    self.write(" ");
3435                    self.write_formatted_comment(comment);
3436                }
3437                Ok(())
3438            }
3439
3440            // DDL statements
3441            Expression::CreateTable(ct) => self.generate_create_table(ct),
3442            Expression::DropTable(dt) => self.generate_drop_table(dt),
3443            Expression::Undrop(u) => self.generate_undrop(u),
3444            Expression::AlterTable(at) => self.generate_alter_table(at),
3445            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3446            Expression::DropIndex(di) => self.generate_drop_index(di),
3447            Expression::CreateView(cv) => self.generate_create_view(cv),
3448            Expression::DropView(dv) => self.generate_drop_view(dv),
3449            Expression::AlterView(av) => self.generate_alter_view(av),
3450            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3451            Expression::Truncate(tr) => self.generate_truncate(tr),
3452            Expression::Use(u) => self.generate_use(u),
3453            // Phase 4: Additional DDL statements
3454            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3455            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3456            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3457            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3458            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3459            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3460            Expression::DropFunction(df) => self.generate_drop_function(df),
3461            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3462            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3463            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3464            Expression::CreateSynonym(cs) => {
3465                self.write_keyword("CREATE SYNONYM");
3466                self.write_space();
3467                self.generate_table(&cs.name)?;
3468                self.write_space();
3469                self.write_keyword("FOR");
3470                self.write_space();
3471                self.generate_table(&cs.target)?;
3472                Ok(())
3473            }
3474            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3475            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3476            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3477            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3478            Expression::CreateType(ct) => self.generate_create_type(ct),
3479            Expression::DropType(dt) => self.generate_drop_type(dt),
3480            Expression::Describe(d) => self.generate_describe(d),
3481            Expression::Show(s) => self.generate_show(s),
3482
3483            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3484            Expression::Cache(c) => self.generate_cache(c),
3485            Expression::Uncache(u) => self.generate_uncache(u),
3486            Expression::LoadData(l) => self.generate_load_data(l),
3487            Expression::Pragma(p) => self.generate_pragma(p),
3488            Expression::Grant(g) => self.generate_grant(g),
3489            Expression::Revoke(r) => self.generate_revoke(r),
3490            Expression::Comment(c) => self.generate_comment(c),
3491            Expression::SetStatement(s) => self.generate_set_statement(s),
3492
3493            // PIVOT/UNPIVOT
3494            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3495            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3496
3497            // VALUES table constructor
3498            Expression::Values(values) => self.generate_values(values),
3499
3500            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3501            Expression::AIAgg(e) => self.generate_ai_agg(e),
3502            Expression::AIClassify(e) => self.generate_ai_classify(e),
3503            Expression::AddPartition(e) => self.generate_add_partition(e),
3504            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3505            Expression::Aliases(e) => self.generate_aliases(e),
3506            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3507            Expression::AlterColumn(e) => self.generate_alter_column(e),
3508            Expression::AlterSession(e) => self.generate_alter_session(e),
3509            Expression::AlterSet(e) => self.generate_alter_set(e),
3510            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3511            Expression::Analyze(e) => self.generate_analyze(e),
3512            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3513            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3514            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3515            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3516            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3517            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3518            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3519            Expression::Anonymous(e) => self.generate_anonymous(e),
3520            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3521            Expression::Apply(e) => self.generate_apply(e),
3522            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3523            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3524            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3525            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3526            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3527            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3528            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3529            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3530            Expression::ArgMax(e) => self.generate_arg_max(e),
3531            Expression::ArgMin(e) => self.generate_arg_min(e),
3532            Expression::ArrayAll(e) => self.generate_array_all(e),
3533            Expression::ArrayAny(e) => self.generate_array_any(e),
3534            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3535            Expression::ArraySum(e) => self.generate_array_sum(e),
3536            Expression::AtIndex(e) => self.generate_at_index(e),
3537            Expression::Attach(e) => self.generate_attach(e),
3538            Expression::AttachOption(e) => self.generate_attach_option(e),
3539            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3540            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3541            Expression::BackupProperty(e) => self.generate_backup_property(e),
3542            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3543            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3544            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3545            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3546            Expression::Booland(e) => self.generate_booland(e),
3547            Expression::Boolor(e) => self.generate_boolor(e),
3548            Expression::BuildProperty(e) => self.generate_build_property(e),
3549            Expression::ByteString(e) => self.generate_byte_string(e),
3550            Expression::CaseSpecificColumnConstraint(e) => {
3551                self.generate_case_specific_column_constraint(e)
3552            }
3553            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3554            Expression::Changes(e) => self.generate_changes(e),
3555            Expression::CharacterSetColumnConstraint(e) => {
3556                self.generate_character_set_column_constraint(e)
3557            }
3558            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3559            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3560            Expression::AssumeColumnConstraint(e) => self.generate_assume_column_constraint(e),
3561            Expression::CheckJson(e) => self.generate_check_json(e),
3562            Expression::CheckXml(e) => self.generate_check_xml(e),
3563            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3564            Expression::Clone(e) => self.generate_clone(e),
3565            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3566            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3567            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3568            Expression::CollateProperty(e) => self.generate_collate_property(e),
3569            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3570            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3571            Expression::ColumnPosition(e) => self.generate_column_position(e),
3572            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3573            Expression::Columns(e) => self.generate_columns(e),
3574            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3575            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3576            Expression::Commit(e) => self.generate_commit(e),
3577            Expression::Comprehension(e) => self.generate_comprehension(e),
3578            Expression::Compress(e) => self.generate_compress(e),
3579            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3580            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3581            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3582            Expression::Constraint(e) => self.generate_constraint(e),
3583            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3584            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3585            Expression::Copy(e) => self.generate_copy(e),
3586            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3587            Expression::Corr(e) => self.generate_corr(e),
3588            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3589            Expression::CovarPop(e) => self.generate_covar_pop(e),
3590            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3591            Expression::Credentials(e) => self.generate_credentials(e),
3592            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3593            Expression::Cte(e) => self.generate_cte(e),
3594            Expression::Cube(e) => self.generate_cube(e),
3595            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3596            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3597            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3598            Expression::CurrentUser(e) => self.generate_current_user(e),
3599            Expression::DPipe(e) => self.generate_d_pipe(e),
3600            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3601            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3602            Expression::Date(e) => self.generate_date_func(e),
3603            Expression::DateBin(e) => self.generate_date_bin(e),
3604            Expression::DateFormatColumnConstraint(e) => {
3605                self.generate_date_format_column_constraint(e)
3606            }
3607            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3608            Expression::Datetime(e) => self.generate_datetime(e),
3609            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3610            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3611            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3612            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3613            Expression::Dayname(e) => self.generate_dayname(e),
3614            Expression::Declare(e) => self.generate_declare(e),
3615            Expression::DeclareItem(e) => self.generate_declare_item(e),
3616            Expression::DecodeCase(e) => self.generate_decode_case(e),
3617            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3618            Expression::DecompressString(e) => self.generate_decompress_string(e),
3619            Expression::Decrypt(e) => self.generate_decrypt(e),
3620            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3621            Expression::DefaultColumnConstraint(e) => {
3622                self.write_keyword("DEFAULT");
3623                self.write_space();
3624                self.generate_expression(&e.this)?;
3625                if let Some(ref col) = e.for_column {
3626                    self.write_space();
3627                    self.write_keyword("FOR");
3628                    self.write_space();
3629                    self.generate_identifier(col)?;
3630                }
3631                Ok(())
3632            }
3633            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3634            Expression::Detach(e) => self.generate_detach(e),
3635            Expression::DictProperty(e) => self.generate_dict_property(e),
3636            Expression::DictRange(e) => self.generate_dict_range(e),
3637            Expression::Directory(e) => self.generate_directory(e),
3638            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3639            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3640            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3641            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3642            Expression::DotProduct(e) => self.generate_dot_product(e),
3643            Expression::DropPartition(e) => self.generate_drop_partition(e),
3644            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3645            Expression::Elt(e) => self.generate_elt(e),
3646            Expression::Encode(e) => self.generate_encode(e),
3647            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3648            Expression::Encrypt(e) => self.generate_encrypt(e),
3649            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3650            Expression::EngineProperty(e) => self.generate_engine_property(e),
3651            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3652            Expression::EphemeralColumnConstraint(e) => {
3653                self.generate_ephemeral_column_constraint(e)
3654            }
3655            Expression::EqualNull(e) => self.generate_equal_null(e),
3656            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3657            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3658            Expression::Export(e) => self.generate_export(e),
3659            Expression::ExternalProperty(e) => self.generate_external_property(e),
3660            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3661            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3662            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3663            Expression::Fetch(e) => self.generate_fetch(e),
3664            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3665            Expression::Filter(e) => self.generate_filter(e),
3666            Expression::Float64(e) => self.generate_float64(e),
3667            Expression::ForIn(e) => self.generate_for_in(e),
3668            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3669            Expression::Format(e) => self.generate_format(e),
3670            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3671            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3672            Expression::From(e) => self.generate_from(e),
3673            Expression::FromBase(e) => self.generate_from_base(e),
3674            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3675            Expression::GapFill(e) => self.generate_gap_fill(e),
3676            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3677            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3678            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3679            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3680            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3681                self.generate_generated_as_identity_column_constraint(e)
3682            }
3683            Expression::GeneratedAsRowColumnConstraint(e) => {
3684                self.generate_generated_as_row_column_constraint(e)
3685            }
3686            Expression::Get(e) => self.generate_get(e),
3687            Expression::GetExtract(e) => self.generate_get_extract(e),
3688            Expression::Getbit(e) => self.generate_getbit(e),
3689            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3690            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3691            Expression::Group(e) => self.generate_group(e),
3692            Expression::GroupBy(e) => self.generate_group_by(e),
3693            Expression::Grouping(e) => self.generate_grouping(e),
3694            Expression::GroupingId(e) => self.generate_grouping_id(e),
3695            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3696            Expression::HashAgg(e) => self.generate_hash_agg(e),
3697            Expression::Having(e) => self.generate_having(e),
3698            Expression::HavingMax(e) => self.generate_having_max(e),
3699            Expression::Heredoc(e) => self.generate_heredoc(e),
3700            Expression::HexEncode(e) => self.generate_hex_encode(e),
3701            Expression::Hll(e) => self.generate_hll(e),
3702            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3703            Expression::IncludeProperty(e) => self.generate_include_property(e),
3704            Expression::Index(e) => self.generate_index(e),
3705            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3706            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3707            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3708            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3709            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3710            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3711            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3712            Expression::Install(e) => self.generate_install(e),
3713            Expression::IntervalOp(e) => self.generate_interval_op(e),
3714            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3715            Expression::IntoClause(e) => self.generate_into_clause(e),
3716            Expression::Introducer(e) => self.generate_introducer(e),
3717            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3718            Expression::JSON(e) => self.generate_json(e),
3719            Expression::JSONArray(e) => self.generate_json_array(e),
3720            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3721            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3722            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3723            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3724            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3725            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3726            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3727            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3728            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3729            Expression::JSONExists(e) => self.generate_json_exists(e),
3730            Expression::JSONCast(e) => self.generate_json_cast(e),
3731            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3732            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3733            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3734            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3735            Expression::JSONFormat(e) => self.generate_json_format(e),
3736            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3737            Expression::JSONKeys(e) => self.generate_json_keys(e),
3738            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3739            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3740            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3741            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3742            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3743            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3744            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3745            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3746            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3747            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3748            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3749            Expression::JSONRemove(e) => self.generate_json_remove(e),
3750            Expression::JSONSchema(e) => self.generate_json_schema(e),
3751            Expression::JSONSet(e) => self.generate_json_set(e),
3752            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3753            Expression::JSONTable(e) => self.generate_json_table(e),
3754            Expression::JSONType(e) => self.generate_json_type(e),
3755            Expression::JSONValue(e) => self.generate_json_value(e),
3756            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3757            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3758            Expression::JoinHint(e) => self.generate_join_hint(e),
3759            Expression::JournalProperty(e) => self.generate_journal_property(e),
3760            Expression::LanguageProperty(e) => self.generate_language_property(e),
3761            Expression::Lateral(e) => self.generate_lateral(e),
3762            Expression::LikeProperty(e) => self.generate_like_property(e),
3763            Expression::Limit(e) => self.generate_limit(e),
3764            Expression::LimitOptions(e) => self.generate_limit_options(e),
3765            Expression::List(e) => self.generate_list(e),
3766            Expression::ToMap(e) => self.generate_tomap(e),
3767            Expression::Localtime(e) => self.generate_localtime(e),
3768            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3769            Expression::LocationProperty(e) => self.generate_location_property(e),
3770            Expression::Lock(e) => self.generate_lock(e),
3771            Expression::LockProperty(e) => self.generate_lock_property(e),
3772            Expression::LockingProperty(e) => self.generate_locking_property(e),
3773            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3774            Expression::LogProperty(e) => self.generate_log_property(e),
3775            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3776            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3777            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3778            Expression::MakeInterval(e) => self.generate_make_interval(e),
3779            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3780            Expression::Map(e) => self.generate_map(e),
3781            Expression::MapCat(e) => self.generate_map_cat(e),
3782            Expression::MapDelete(e) => self.generate_map_delete(e),
3783            Expression::MapInsert(e) => self.generate_map_insert(e),
3784            Expression::MapPick(e) => self.generate_map_pick(e),
3785            Expression::MaskingPolicyColumnConstraint(e) => {
3786                self.generate_masking_policy_column_constraint(e)
3787            }
3788            Expression::MatchAgainst(e) => self.generate_match_against(e),
3789            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3790            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3791            Expression::Merge(e) => self.generate_merge(e),
3792            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3793            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3794            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3795            Expression::Minhash(e) => self.generate_minhash(e),
3796            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3797            Expression::Monthname(e) => self.generate_monthname(e),
3798            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3799            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3800            Expression::Normal(e) => self.generate_normal(e),
3801            Expression::Normalize(e) => self.generate_normalize(e),
3802            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3803            Expression::Nullif(e) => self.generate_nullif(e),
3804            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3805            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3806            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3807            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3808            Expression::Offset(e) => self.generate_offset(e),
3809            Expression::Qualify(e) => self.generate_qualify(e),
3810            Expression::OnCluster(e) => self.generate_on_cluster(e),
3811            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3812            Expression::OnCondition(e) => self.generate_on_condition(e),
3813            Expression::OnConflict(e) => self.generate_on_conflict(e),
3814            Expression::OnProperty(e) => self.generate_on_property(e),
3815            Expression::Opclass(e) => self.generate_opclass(e),
3816            Expression::OpenJSON(e) => self.generate_open_json(e),
3817            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3818            Expression::Operator(e) => self.generate_operator(e),
3819            Expression::OrderBy(e) => self.generate_order_by(e),
3820            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3821            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3822            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3823            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3824            Expression::ParseIp(e) => self.generate_parse_ip(e),
3825            Expression::ParseJSON(e) => self.generate_parse_json(e),
3826            Expression::ParseTime(e) => self.generate_parse_time(e),
3827            Expression::ParseUrl(e) => self.generate_parse_url(e),
3828            Expression::Partition(e) => self.generate_partition_expr(e),
3829            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3830            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3831            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3832            Expression::PartitionByRangePropertyDynamic(e) => {
3833                self.generate_partition_by_range_property_dynamic(e)
3834            }
3835            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3836            Expression::PartitionList(e) => self.generate_partition_list(e),
3837            Expression::PartitionRange(e) => self.generate_partition_range(e),
3838            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3839            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3840            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3841            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3842            Expression::PeriodForSystemTimeConstraint(e) => {
3843                self.generate_period_for_system_time_constraint(e)
3844            }
3845            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3846            Expression::PivotAny(e) => self.generate_pivot_any(e),
3847            Expression::Predict(e) => self.generate_predict(e),
3848            Expression::PreviousDay(e) => self.generate_previous_day(e),
3849            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3850            Expression::PrimaryKeyColumnConstraint(e) => {
3851                self.generate_primary_key_column_constraint(e)
3852            }
3853            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3854            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3855            Expression::OptionsProperty(e) => self.generate_options_property(e),
3856            Expression::Properties(e) => self.generate_properties(e),
3857            Expression::Property(e) => self.generate_property(e),
3858            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3859            Expression::Put(e) => self.generate_put(e),
3860            Expression::Quantile(e) => self.generate_quantile(e),
3861            Expression::QueryBand(e) => self.generate_query_band(e),
3862            Expression::QueryOption(e) => self.generate_query_option(e),
3863            Expression::QueryTransform(e) => self.generate_query_transform(e),
3864            Expression::Randn(e) => self.generate_randn(e),
3865            Expression::Randstr(e) => self.generate_randstr(e),
3866            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3867            Expression::RangeN(e) => self.generate_range_n(e),
3868            Expression::ReadCSV(e) => self.generate_read_csv(e),
3869            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3870            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3871            Expression::Reduce(e) => self.generate_reduce(e),
3872            Expression::Reference(e) => self.generate_reference(e),
3873            Expression::Refresh(e) => self.generate_refresh(e),
3874            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3875            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3876            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3877            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3878            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3879            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3880            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3881            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3882            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3883            Expression::RegrCount(e) => self.generate_regr_count(e),
3884            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3885            Expression::RegrR2(e) => self.generate_regr_r2(e),
3886            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3887            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3888            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3889            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3890            Expression::RegrValx(e) => self.generate_regr_valx(e),
3891            Expression::RegrValy(e) => self.generate_regr_valy(e),
3892            Expression::RemoteWithConnectionModelProperty(e) => {
3893                self.generate_remote_with_connection_model_property(e)
3894            }
3895            Expression::RenameColumn(e) => self.generate_rename_column(e),
3896            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3897            Expression::Returning(e) => self.generate_returning(e),
3898            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3899            Expression::Rollback(e) => self.generate_rollback(e),
3900            Expression::Rollup(e) => self.generate_rollup(e),
3901            Expression::RowFormatDelimitedProperty(e) => {
3902                self.generate_row_format_delimited_property(e)
3903            }
3904            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3905            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3906            Expression::SHA2(e) => self.generate_sha2(e),
3907            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3908            Expression::SafeAdd(e) => self.generate_safe_add(e),
3909            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3910            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3911            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3912            Expression::SampleProperty(e) => self.generate_sample_property(e),
3913            Expression::Schema(e) => self.generate_schema(e),
3914            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3915            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3916            Expression::Search(e) => self.generate_search(e),
3917            Expression::SearchIp(e) => self.generate_search_ip(e),
3918            Expression::SecurityProperty(e) => self.generate_security_property(e),
3919            Expression::SemanticView(e) => self.generate_semantic_view(e),
3920            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3921            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3922            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3923            Expression::Set(e) => self.generate_set(e),
3924            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3925            Expression::SetItem(e) => self.generate_set_item(e),
3926            Expression::SetOperation(e) => self.generate_set_operation(e),
3927            Expression::SetProperty(e) => self.generate_set_property(e),
3928            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3929            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3930            Expression::Slice(e) => self.generate_slice(e),
3931            Expression::SortArray(e) => self.generate_sort_array(e),
3932            Expression::SortBy(e) => self.generate_sort_by(e),
3933            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3934            Expression::SplitPart(e) => self.generate_split_part(e),
3935            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3936            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3937            Expression::StDistance(e) => self.generate_st_distance(e),
3938            Expression::StPoint(e) => self.generate_st_point(e),
3939            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3940            Expression::StandardHash(e) => self.generate_standard_hash(e),
3941            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3942            Expression::StrPosition(e) => self.generate_str_position(e),
3943            Expression::StrToDate(e) => self.generate_str_to_date(e),
3944            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3945            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3946            Expression::StrToMap(e) => self.generate_str_to_map(e),
3947            Expression::StrToTime(e) => self.generate_str_to_time(e),
3948            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3949            Expression::StringToArray(e) => self.generate_string_to_array(e),
3950            Expression::Struct(e) => self.generate_struct(e),
3951            Expression::Stuff(e) => self.generate_stuff(e),
3952            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3953            Expression::Summarize(e) => self.generate_summarize(e),
3954            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3955            Expression::TableAlias(e) => self.generate_table_alias(e),
3956            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3957            Expression::RowsFrom(e) => self.generate_rows_from(e),
3958            Expression::TableSample(e) => self.generate_table_sample(e),
3959            Expression::Tag(e) => self.generate_tag(e),
3960            Expression::Tags(e) => self.generate_tags(e),
3961            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3962            Expression::Time(e) => self.generate_time_func(e),
3963            Expression::TimeAdd(e) => self.generate_time_add(e),
3964            Expression::TimeDiff(e) => self.generate_time_diff(e),
3965            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3966            Expression::TimeSlice(e) => self.generate_time_slice(e),
3967            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3968            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3969            Expression::TimeSub(e) => self.generate_time_sub(e),
3970            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3971            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3972            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3973            Expression::TimeUnit(e) => self.generate_time_unit(e),
3974            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3975            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3976            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3977            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3978            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3979            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3980            Expression::ToBinary(e) => self.generate_to_binary(e),
3981            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3982            Expression::ToChar(e) => self.generate_to_char(e),
3983            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3984            Expression::ToDouble(e) => self.generate_to_double(e),
3985            Expression::ToFile(e) => self.generate_to_file(e),
3986            Expression::ToNumber(e) => self.generate_to_number(e),
3987            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3988            Expression::Transaction(e) => self.generate_transaction(e),
3989            Expression::Transform(e) => self.generate_transform(e),
3990            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3991            Expression::TransientProperty(e) => self.generate_transient_property(e),
3992            Expression::Translate(e) => self.generate_translate(e),
3993            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3994            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3995            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3996            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3997            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3998            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3999            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
4000            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
4001            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
4002            Expression::Unhex(e) => self.generate_unhex(e),
4003            Expression::UnicodeString(e) => self.generate_unicode_string(e),
4004            Expression::Uniform(e) => self.generate_uniform(e),
4005            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
4006            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
4007            Expression::RollupProperty(e) => self.generate_rollup_property(e),
4008            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
4009            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
4010            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
4011            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
4012            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
4013            Expression::UtcTime(e) => self.generate_utc_time(e),
4014            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
4015            Expression::Uuid(e) => self.generate_uuid(e),
4016            Expression::Var(v) => {
4017                if matches!(self.config.dialect, Some(DialectType::MySQL))
4018                    && v.this.len() > 2
4019                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
4020                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
4021                {
4022                    return self.generate_identifier(&Identifier {
4023                        name: v.this.clone(),
4024                        quoted: true,
4025                        trailing_comments: Vec::new(),
4026                        span: None,
4027                    });
4028                }
4029                self.write(&v.this);
4030                Ok(())
4031            }
4032            Expression::Variadic(e) => {
4033                self.write_keyword("VARIADIC");
4034                self.write_space();
4035                self.generate_expression(&e.this)?;
4036                Ok(())
4037            }
4038            Expression::VarMap(e) => self.generate_var_map(e),
4039            Expression::VectorSearch(e) => self.generate_vector_search(e),
4040            Expression::Version(e) => self.generate_version(e),
4041            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
4042            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
4043            Expression::WatermarkColumnConstraint(e) => {
4044                self.generate_watermark_column_constraint(e)
4045            }
4046            Expression::Week(e) => self.generate_week(e),
4047            Expression::When(e) => self.generate_when(e),
4048            Expression::Whens(e) => self.generate_whens(e),
4049            Expression::Where(e) => self.generate_where(e),
4050            Expression::WidthBucket(e) => self.generate_width_bucket(e),
4051            Expression::Window(e) => self.generate_window(e),
4052            Expression::WindowSpec(e) => self.generate_window_spec(e),
4053            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
4054            Expression::WithFill(e) => self.generate_with_fill(e),
4055            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
4056            Expression::WithOperator(e) => self.generate_with_operator(e),
4057            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
4058            Expression::WithSchemaBindingProperty(e) => {
4059                self.generate_with_schema_binding_property(e)
4060            }
4061            Expression::WithSystemVersioningProperty(e) => {
4062                self.generate_with_system_versioning_property(e)
4063            }
4064            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
4065            Expression::XMLElement(e) => self.generate_xml_element(e),
4066            Expression::XMLGet(e) => self.generate_xml_get(e),
4067            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
4068            Expression::XMLTable(e) => self.generate_xml_table(e),
4069            Expression::Xor(e) => self.generate_xor(e),
4070            Expression::Zipf(e) => self.generate_zipf(e),
4071            _ => self.write_unsupported_comment("unsupported expression"),
4072        }
4073    }
4074
4075    fn should_handle_tsql_distinct_null_ordering(&self, select: &Select) -> bool {
4076        matches!(
4077            self.config.dialect,
4078            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4079        ) && !self.config.null_ordering_supported
4080            && select.distinct
4081            && select.order_by.as_ref().is_some_and(|order_by| {
4082                order_by
4083                    .expressions
4084                    .iter()
4085                    .any(Self::ordered_requires_tsql_null_ordering_emulation)
4086            })
4087    }
4088
4089    fn ordered_requires_tsql_null_ordering_emulation(ordered: &Ordered) -> bool {
4090        let Some(nulls_first) = ordered.nulls_first else {
4091            return false;
4092        };
4093
4094        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
4095        let target_default_nulls_first = !ordered.desc;
4096
4097        nulls_first != target_default_nulls_first && !random_ordering
4098    }
4099
4100    fn projection_output_identifier(expression: &Expression) -> Option<Identifier> {
4101        match expression {
4102            Expression::Alias(alias) if !alias.alias.name.is_empty() => Some(alias.alias.clone()),
4103            Expression::Column(column) => Some(column.name.clone()),
4104            Expression::Identifier(identifier) => Some(identifier.clone()),
4105            _ => None,
4106        }
4107    }
4108
4109    fn identifier_names_match(left: &Identifier, right: &Identifier) -> bool {
4110        if left.quoted || right.quoted {
4111            left.name == right.name
4112        } else {
4113            left.name.eq_ignore_ascii_case(&right.name)
4114        }
4115    }
4116
4117    fn expression_matches_identifier(expression: &Expression, identifier: &Identifier) -> bool {
4118        match expression {
4119            Expression::Column(column) if column.table.is_none() => {
4120                Self::identifier_names_match(&column.name, identifier)
4121            }
4122            Expression::Identifier(other) => Self::identifier_names_match(other, identifier),
4123            _ => false,
4124        }
4125    }
4126
4127    fn column_expression(identifier: Identifier) -> Expression {
4128        Expression::Column(Box::new(Column {
4129            name: identifier,
4130            table: None,
4131            join_mark: false,
4132            trailing_comments: Vec::new(),
4133            span: None,
4134            inferred_type: None,
4135        }))
4136    }
4137
4138    fn positional_ordering_index(expression: &Expression) -> Option<usize> {
4139        match expression {
4140            Expression::Literal(lit) => match lit.as_ref() {
4141                Literal::Number(n) => {
4142                    let value = n.parse::<usize>().ok()?;
4143                    value.checked_sub(1)
4144                }
4145                _ => None,
4146            },
4147            _ => None,
4148        }
4149    }
4150
4151    fn projection_sort_expression(expression: &Expression) -> Option<Expression> {
4152        let expression = match expression {
4153            Expression::Alias(alias) => &alias.this,
4154            other => other,
4155        };
4156
4157        match expression {
4158            Expression::Star(_)
4159            | Expression::Literal(_)
4160            | Expression::Null(_)
4161            | Expression::Boolean(_) => None,
4162            _ => Some(expression.clone()),
4163        }
4164    }
4165
4166    fn resolve_positional_order_projection(
4167        projections: &[Expression],
4168        index: usize,
4169    ) -> Option<Expression> {
4170        Self::projection_sort_expression(projections.get(index)?)
4171    }
4172
4173    fn is_direct_grouping_expression(expression: &Expression) -> bool {
4174        match expression {
4175            Expression::Paren(paren) => Self::is_direct_grouping_expression(&paren.this),
4176            Expression::Grouping(_) | Expression::GroupingId(_) => true,
4177            Expression::Function(function) => {
4178                function.name.eq_ignore_ascii_case("GROUPING")
4179                    || function.name.eq_ignore_ascii_case("GROUPING_ID")
4180            }
4181            _ => false,
4182        }
4183    }
4184
4185    fn group_by_has_multiple_grouping_levels(group_by: &GroupBy) -> bool {
4186        group_by
4187            .expressions
4188            .iter()
4189            .any(|expression| match expression {
4190                Expression::Cube(_) | Expression::Rollup(_) => true,
4191                Expression::GroupingSets(grouping_sets) => grouping_sets.expressions.len() > 1,
4192                Expression::Function(function)
4193                    if function.name.eq_ignore_ascii_case("CUBE")
4194                        || function.name.eq_ignore_ascii_case("ROLLUP") =>
4195                {
4196                    !function.args.is_empty()
4197                }
4198                Expression::Function(function)
4199                    if function.name.eq_ignore_ascii_case("GROUPING SETS") =>
4200                {
4201                    function.args.len() > 1
4202                        || function.args.first().is_some_and(|expression| {
4203                            matches!(expression, Expression::Cube(_) | Expression::Rollup(_))
4204                                || matches!(expression,
4205                                    Expression::Function(nested)
4206                                        if nested.name.eq_ignore_ascii_case("CUBE")
4207                                            || nested.name.eq_ignore_ascii_case("ROLLUP")
4208                                )
4209                        })
4210                }
4211                _ => false,
4212            })
4213    }
4214
4215    fn set_output_identifier_at(expression: &Expression, index: usize) -> Option<Identifier> {
4216        match expression {
4217            Expression::Select(select) => {
4218                Self::projection_output_identifier(select.expressions.get(index)?)
4219            }
4220            Expression::Union(union) => Self::set_output_identifier_at(&union.left, index),
4221            Expression::Intersect(intersect) => {
4222                Self::set_output_identifier_at(&intersect.left, index)
4223            }
4224            Expression::Except(except) => Self::set_output_identifier_at(&except.left, index),
4225            Expression::Subquery(subquery) => subquery
4226                .column_aliases
4227                .get(index)
4228                .cloned()
4229                .or_else(|| Self::set_output_identifier_at(&subquery.this, index)),
4230            _ => None,
4231        }
4232    }
4233
4234    fn resolve_single_subquery_star_projection(
4235        select: &Select,
4236        index: usize,
4237    ) -> Option<Expression> {
4238        if select.expressions.len() != 1
4239            || !matches!(select.expressions.first(), Some(Expression::Star(_)))
4240        {
4241            return None;
4242        }
4243
4244        let from = select.from.as_ref()?;
4245        if from.expressions.len() != 1 {
4246            return None;
4247        }
4248
4249        let Expression::Subquery(subquery) = from.expressions.first()? else {
4250            return None;
4251        };
4252
4253        subquery
4254            .column_aliases
4255            .get(index)
4256            .cloned()
4257            .or_else(|| Self::set_output_identifier_at(&subquery.this, index))
4258            .map(Self::column_expression)
4259    }
4260
4261    fn sole_tsql_ordering_source_qualifier(select: &Select) -> Option<Identifier> {
4262        if !select.joins.is_empty() {
4263            return None;
4264        }
4265
4266        let from = select.from.as_ref()?;
4267        if from.expressions.len() != 1 {
4268            return None;
4269        }
4270
4271        match from.expressions.first()? {
4272            Expression::Table(table) => {
4273                Some(table.alias.clone().unwrap_or_else(|| table.name.clone()))
4274            }
4275            Expression::Subquery(subquery) => subquery.alias.clone(),
4276            _ => None,
4277        }
4278    }
4279
4280    fn resolve_duplicate_tsql_ordering_column(
4281        select: &Select,
4282        expression: &Expression,
4283    ) -> Option<Expression> {
4284        let Expression::Column(order_column) = expression else {
4285            return None;
4286        };
4287        if order_column.table.is_some() {
4288            return None;
4289        }
4290
4291        let matching_projections: Vec<_> = select
4292            .expressions
4293            .iter()
4294            .filter(|projection| {
4295                Self::projection_output_identifier(projection).is_some_and(|identifier| {
4296                    Self::identifier_names_match(&identifier, &order_column.name)
4297                })
4298            })
4299            .filter_map(Self::projection_sort_expression)
4300            .collect();
4301
4302        if matching_projections.len() < 2
4303            || matching_projections[1..]
4304                .iter()
4305                .any(|projection| projection != &matching_projections[0])
4306        {
4307            return None;
4308        }
4309
4310        let Expression::Column(projected_column) = &matching_projections[0] else {
4311            return None;
4312        };
4313        if !Self::identifier_names_match(&projected_column.name, &order_column.name) {
4314            return None;
4315        }
4316
4317        let qualifier = projected_column
4318            .table
4319            .clone()
4320            .or_else(|| Self::sole_tsql_ordering_source_qualifier(select))?;
4321        let mut resolved = order_column.as_ref().clone();
4322        resolved.table = Some(qualifier);
4323        Some(Expression::Column(Box::new(resolved)))
4324    }
4325
4326    fn resolve_tsql_null_ordering_for_select(&self, select: &Select) -> Option<Select> {
4327        if !matches!(
4328            self.config.dialect,
4329            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4330        ) || self.config.null_ordering_supported
4331        {
4332            return None;
4333        }
4334
4335        let order_by = select.order_by.as_ref()?;
4336        let grouping_order_is_constant = matches!(self.config.dialect, Some(DialectType::Fabric))
4337            && select
4338                .group_by
4339                .as_ref()
4340                .is_some_and(|group_by| !Self::group_by_has_multiple_grouping_levels(group_by));
4341        let mut resolved_expressions = Vec::with_capacity(order_by.expressions.len());
4342        let mut changed = false;
4343
4344        for mut ordered in order_by.expressions.iter().cloned() {
4345            if Self::ordered_requires_tsql_null_ordering_emulation(&ordered) {
4346                if let Some(index) = Self::positional_ordering_index(&ordered.this) {
4347                    if let Some(resolved) =
4348                        Self::resolve_positional_order_projection(&select.expressions, index)
4349                            .or_else(|| {
4350                                Self::resolve_single_subquery_star_projection(select, index)
4351                            })
4352                    {
4353                        if resolved != ordered.this {
4354                            ordered.this = resolved;
4355                            changed = true;
4356                        }
4357                    }
4358                }
4359
4360                if let Some(resolved) =
4361                    Self::resolve_duplicate_tsql_ordering_column(select, &ordered.this)
4362                {
4363                    ordered.this = resolved;
4364                    changed = true;
4365                }
4366            }
4367
4368            // Fabric rejects ORDER BY expressions that fold to constants (Msg 408).
4369            // With one effective grouping level, a direct GROUPING/GROUPING_ID
4370            // result is constant and contributes no ordering, so omit it.
4371            if grouping_order_is_constant && Self::is_direct_grouping_expression(&ordered.this) {
4372                changed = true;
4373                continue;
4374            }
4375
4376            resolved_expressions.push(ordered);
4377        }
4378
4379        if changed {
4380            let mut resolved_select = select.clone();
4381            resolved_select.order_by = if resolved_expressions.is_empty() {
4382                None
4383            } else {
4384                Some(OrderBy {
4385                    expressions: resolved_expressions,
4386                    siblings: order_by.siblings,
4387                    comments: order_by.comments.clone(),
4388                })
4389            };
4390            Some(resolved_select)
4391        } else {
4392            None
4393        }
4394    }
4395
4396    fn resolve_distinct_order_projection(
4397        projections: &[Expression],
4398        order_expression: &Expression,
4399    ) -> Option<Expression> {
4400        for projection in projections {
4401            if let Expression::Alias(alias) = projection {
4402                if Self::expression_matches_identifier(order_expression, &alias.alias)
4403                    || &alias.this == order_expression
4404                {
4405                    return Some(alias.this.clone());
4406                }
4407            } else if projection == order_expression {
4408                return Some(projection.clone());
4409            } else if let Some(identifier) = Self::projection_output_identifier(projection) {
4410                if Self::expression_matches_identifier(order_expression, &identifier) {
4411                    return Some(projection.clone());
4412                }
4413            }
4414        }
4415
4416        None
4417    }
4418
4419    fn fresh_polyglot_alias(base: &str, index: usize, used_names: &mut Vec<String>) -> String {
4420        let mut suffix = 0;
4421        loop {
4422            let candidate = if suffix == 0 {
4423                format!("{base}_{index}")
4424            } else {
4425                format!("{base}_{index}_{suffix}")
4426            };
4427
4428            if !used_names
4429                .iter()
4430                .any(|name| name.eq_ignore_ascii_case(&candidate))
4431            {
4432                used_names.push(candidate.clone());
4433                return candidate;
4434            }
4435
4436            suffix += 1;
4437        }
4438    }
4439
4440    fn tsql_null_ordering_case(expression: Expression) -> Expression {
4441        Expression::Case(Box::new(Case {
4442            operand: None,
4443            whens: vec![(
4444                Expression::IsNull(Box::new(IsNull {
4445                    this: expression,
4446                    not: false,
4447                    postfix_form: false,
4448                })),
4449                Expression::number(1),
4450            )],
4451            else_: Some(Expression::number(0)),
4452            comments: Vec::new(),
4453            inferred_type: None,
4454        }))
4455    }
4456
4457    fn try_build_tsql_distinct_null_ordering_wrapper(&self, select: &Select) -> Option<Select> {
4458        let order_by = select.order_by.as_ref()?;
4459
4460        if order_by.siblings
4461            || select.distinct_on.is_some()
4462            || select.distribute_by.is_some()
4463            || select.cluster_by.is_some()
4464            || select.sort_by.is_some()
4465            || select.limit.is_some()
4466            || select.offset.is_some()
4467            || select.limit_by.is_some()
4468            || select.fetch.is_some()
4469            || select.top.is_some()
4470            || select.settings.is_some()
4471            || select.format.is_some()
4472            || select.kind.is_some()
4473            || select.hint.is_some()
4474            || select.into.is_some()
4475            || !select.locks.is_empty()
4476            || !select.for_xml.is_empty()
4477            || !select.for_json.is_empty()
4478            || !select.operation_modifiers.is_empty()
4479            || select.option.is_some()
4480            || select.exclude.is_some()
4481        {
4482            return None;
4483        }
4484
4485        let projection_identifiers: Vec<_> = select
4486            .expressions
4487            .iter()
4488            .map(Self::projection_output_identifier)
4489            .collect::<Option<_>>()?;
4490        let mut used_names: Vec<_> = projection_identifiers
4491            .iter()
4492            .map(|identifier| identifier.name.clone())
4493            .collect();
4494
4495        let mut inner = select.clone();
4496        inner.order_by = None;
4497
4498        let outer_with = inner.with.take();
4499        let outer_leading_comments = std::mem::take(&mut inner.leading_comments);
4500        let outer_post_select_comments = std::mem::take(&mut inner.post_select_comments);
4501
4502        let mut outer_order_expressions = Vec::with_capacity(order_by.expressions.len() * 2);
4503        for (index, ordered) in order_by.expressions.iter().enumerate() {
4504            if ordered.with_fill.is_some() {
4505                return None;
4506            }
4507
4508            let sort_expression =
4509                Self::resolve_distinct_order_projection(&select.expressions, &ordered.this)?;
4510
4511            if Self::ordered_requires_tsql_null_ordering_emulation(ordered) {
4512                let null_alias =
4513                    Self::fresh_polyglot_alias("_polyglot_order_null", index, &mut used_names);
4514                inner.expressions.push(
4515                    Self::tsql_null_ordering_case(sort_expression.clone())
4516                        .alias(null_alias.clone()),
4517                );
4518                outer_order_expressions.push(Ordered {
4519                    this: Expression::column(null_alias),
4520                    desc: ordered.nulls_first == Some(true),
4521                    nulls_first: None,
4522                    explicit_asc: false,
4523                    with_fill: None,
4524                });
4525            }
4526
4527            let key_alias =
4528                Self::fresh_polyglot_alias("_polyglot_order_key", index, &mut used_names);
4529            inner
4530                .expressions
4531                .push(sort_expression.alias(key_alias.clone()));
4532            outer_order_expressions.push(Ordered {
4533                this: Expression::column(key_alias),
4534                desc: ordered.desc,
4535                nulls_first: None,
4536                explicit_asc: ordered.explicit_asc,
4537                with_fill: None,
4538            });
4539        }
4540
4541        let subquery = Subquery {
4542            this: Expression::Select(Box::new(inner)),
4543            alias: Some(Identifier::new("_polyglot_distinct_order")),
4544            column_aliases: Vec::new(),
4545            alias_explicit_as: true,
4546            alias_keyword: None,
4547            order_by: None,
4548            limit: None,
4549            offset: None,
4550            distribute_by: None,
4551            sort_by: None,
4552            cluster_by: None,
4553            lateral: false,
4554            modifiers_inside: false,
4555            trailing_comments: Vec::new(),
4556            inferred_type: None,
4557        };
4558
4559        let mut outer = Select::new();
4560        outer.with = outer_with;
4561        outer.leading_comments = outer_leading_comments;
4562        outer.post_select_comments = outer_post_select_comments;
4563        outer.expressions = projection_identifiers
4564            .into_iter()
4565            .map(Self::column_expression)
4566            .collect();
4567        outer.from = Some(From {
4568            expressions: vec![Expression::Subquery(Box::new(subquery))],
4569        });
4570        outer.order_by = Some(OrderBy {
4571            expressions: outer_order_expressions,
4572            siblings: false,
4573            comments: order_by.comments.clone(),
4574        });
4575
4576        Some(outer)
4577    }
4578
4579    fn generate_select(&mut self, select: &Select) -> Result<()> {
4580        use crate::dialects::DialectType;
4581
4582        if let Some(resolved_select) = self.resolve_tsql_null_ordering_for_select(select) {
4583            return self.generate_select(&resolved_select);
4584        }
4585
4586        if self.should_handle_tsql_distinct_null_ordering(select) {
4587            if let Some(wrapped_select) = self.try_build_tsql_distinct_null_ordering_wrapper(select)
4588            {
4589                return self.generate_select(&wrapped_select);
4590            }
4591
4592            self.unsupported(
4593                "SELECT DISTINCT with emulated NULL ordering is not supported for TSQL/Fabric",
4594            )?;
4595        }
4596
4597        // Redshift-style EXCLUDE: for dialects other than Redshift, wrap in a derived table
4598        // e.g., SELECT *, col4 EXCLUDE (col2, col3) FROM t
4599        //   → SELECT * EXCLUDE (col2, col3) FROM (SELECT *, col4 FROM t)
4600        if let Some(exclude) = &select.exclude {
4601            if !exclude.is_empty() && !matches!(self.config.dialect, Some(DialectType::Redshift)) {
4602                // Build the inner select (same as original but without exclude)
4603                let mut inner_select = select.clone();
4604                inner_select.exclude = None;
4605                let inner_expr = Expression::Select(Box::new(inner_select));
4606
4607                // Build the subquery
4608                let subquery = crate::expressions::Subquery {
4609                    this: inner_expr,
4610                    alias: None,
4611                    column_aliases: Vec::new(),
4612                    alias_explicit_as: false,
4613                    alias_keyword: None,
4614                    order_by: None,
4615                    limit: None,
4616                    offset: None,
4617                    distribute_by: None,
4618                    sort_by: None,
4619                    cluster_by: None,
4620                    lateral: false,
4621                    modifiers_inside: false,
4622                    trailing_comments: Vec::new(),
4623                    inferred_type: None,
4624                };
4625
4626                // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner)
4627                let star = Expression::Star(crate::expressions::Star {
4628                    table: None,
4629                    except: Some(
4630                        exclude
4631                            .iter()
4632                            .map(|e| match e {
4633                                Expression::Column(col) => col.name.clone(),
4634                                Expression::Identifier(id) => id.clone(),
4635                                _ => crate::expressions::Identifier::new("unknown".to_string()),
4636                            })
4637                            .collect(),
4638                    ),
4639                    replace: None,
4640                    rename: None,
4641                    trailing_comments: Vec::new(),
4642                    span: None,
4643                });
4644
4645                let outer_select = Select {
4646                    expressions: vec![star],
4647                    from: Some(crate::expressions::From {
4648                        expressions: vec![Expression::Subquery(Box::new(subquery))],
4649                    }),
4650                    ..Select::new()
4651                };
4652
4653                return self.generate_select(&outer_select);
4654            }
4655        }
4656
4657        // Output leading comments before SELECT
4658        for comment in &select.leading_comments {
4659            self.write_formatted_comment(comment);
4660            self.write(" ");
4661        }
4662
4663        // WITH clause
4664        if let Some(with) = &select.with {
4665            self.generate_with(with)?;
4666            if self.config.pretty {
4667                self.write_newline();
4668                self.write_indent();
4669            } else {
4670                self.write_space();
4671            }
4672        }
4673
4674        // Output post-SELECT comments (comments that appeared after SELECT keyword)
4675        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
4676        for comment in &select.post_select_comments {
4677            self.write_formatted_comment(comment);
4678            self.write(" ");
4679        }
4680
4681        self.write_keyword("SELECT");
4682
4683        // Generate query hint if present /*+ ... */
4684        if let Some(hint) = &select.hint {
4685            self.generate_hint(hint)?;
4686        }
4687
4688        // For SQL Server, convert LIMIT to TOP (structural transformation)
4689        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
4690        // TOP clause (SQL Server style - before DISTINCT)
4691        let use_top_from_limit = matches!(
4692            self.config.dialect,
4693            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4694        ) && select.top.is_none()
4695            && select
4696                .limit
4697                .as_ref()
4698                .is_some_and(|limit| !Self::is_noop_limit_expr(&limit.this))
4699            && select.offset.is_none(); // Don't use TOP when there's OFFSET
4700
4701        // For TOP-supporting dialects: DISTINCT before TOP
4702        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
4703        let is_top_dialect = matches!(
4704            self.config.dialect,
4705            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
4706        );
4707        let keep_top_verbatim = !is_top_dialect
4708            && select.limit.is_none()
4709            && select
4710                .top
4711                .as_ref()
4712                .map_or(false, |top| top.percent || top.with_ties);
4713
4714        if select.distinct && (is_top_dialect || select.top.is_some()) {
4715            self.write_space();
4716            self.write_keyword("DISTINCT");
4717        }
4718
4719        if is_top_dialect || keep_top_verbatim {
4720            if let Some(top) = &select.top {
4721                self.write_space();
4722                self.write_keyword("TOP");
4723                if top.parenthesized {
4724                    if matches!(&top.this, Expression::Subquery(_) | Expression::Paren(_)) {
4725                        self.write_space();
4726                        self.generate_expression(&top.this)?;
4727                    } else {
4728                        self.write(" (");
4729                        self.generate_expression(&top.this)?;
4730                        self.write(")");
4731                    }
4732                } else {
4733                    self.write_space();
4734                    self.generate_expression(&top.this)?;
4735                }
4736                if top.percent {
4737                    self.write_space();
4738                    self.write_keyword("PERCENT");
4739                }
4740                if top.with_ties {
4741                    self.write_space();
4742                    self.write_keyword("WITH TIES");
4743                }
4744            } else if use_top_from_limit {
4745                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4746                if let Some(limit) = &select.limit {
4747                    self.write_space();
4748                    self.write_keyword("TOP");
4749                    // Use parentheses for complex expressions, but not for simple literals
4750                    let is_simple_literal = matches!(&limit.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4751                    if is_simple_literal {
4752                        self.write_space();
4753                        self.generate_expression(&limit.this)?;
4754                    } else {
4755                        self.write(" (");
4756                        self.generate_expression(&limit.this)?;
4757                        self.write(")");
4758                    }
4759                }
4760            }
4761        }
4762
4763        if select.distinct && !is_top_dialect && select.top.is_none() {
4764            self.write_space();
4765            self.write_keyword("DISTINCT");
4766        }
4767
4768        // DISTINCT ON clause (PostgreSQL)
4769        if let Some(distinct_on) = &select.distinct_on {
4770            self.write_space();
4771            self.write_keyword("ON");
4772            self.write(" (");
4773            for (i, expr) in distinct_on.iter().enumerate() {
4774                if i > 0 {
4775                    self.write(", ");
4776                }
4777                self.generate_expression(expr)?;
4778            }
4779            self.write(")");
4780        }
4781
4782        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4783        for modifier in &select.operation_modifiers {
4784            self.write_space();
4785            self.write_keyword(modifier);
4786        }
4787
4788        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4789        if let Some(kind) = &select.kind {
4790            self.write_space();
4791            self.write_keyword("AS");
4792            self.write_space();
4793            self.write_keyword(kind);
4794        }
4795
4796        // Expressions (only if there are any)
4797        if !select.expressions.is_empty() {
4798            if self.config.pretty {
4799                self.write_newline();
4800                self.indent_level += 1;
4801            } else {
4802                self.write_space();
4803            }
4804        }
4805
4806        for (i, expr) in select.expressions.iter().enumerate() {
4807            if i > 0 {
4808                self.write(",");
4809                if self.config.pretty {
4810                    self.write_newline();
4811                } else {
4812                    self.write_space();
4813                }
4814            }
4815            if self.config.pretty {
4816                self.write_indent();
4817            }
4818            self.generate_expression(expr)?;
4819        }
4820
4821        if self.config.pretty && !select.expressions.is_empty() {
4822            self.indent_level -= 1;
4823        }
4824
4825        // Redshift-style EXCLUDE clause at the end of the projection list
4826        // For Redshift dialect: append EXCLUDE (col1, col2) after the expressions
4827        // For other dialects (DuckDB, Snowflake): this is handled by wrapping in a derived table
4828        // (done after the full select is generated below)
4829        if let Some(exclude) = &select.exclude {
4830            if !exclude.is_empty() && matches!(self.config.dialect, Some(DialectType::Redshift)) {
4831                self.write_space();
4832                self.write_keyword("EXCLUDE");
4833                self.write(" (");
4834                for (i, col) in exclude.iter().enumerate() {
4835                    if i > 0 {
4836                        self.write(", ");
4837                    }
4838                    self.generate_expression(col)?;
4839                }
4840                self.write(")");
4841            }
4842        }
4843
4844        // INTO clause (SELECT ... INTO table_name)
4845        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4846        if let Some(into) = &select.into {
4847            if self.config.pretty {
4848                self.write_newline();
4849                self.write_indent();
4850            } else {
4851                self.write_space();
4852            }
4853            if into.bulk_collect {
4854                self.write_keyword("BULK COLLECT INTO");
4855            } else {
4856                self.write_keyword("INTO");
4857            }
4858            if into.temporary {
4859                self.write_space();
4860                self.write_keyword("TEMPORARY");
4861            }
4862            if into.unlogged {
4863                self.write_space();
4864                self.write_keyword("UNLOGGED");
4865            }
4866            self.write_space();
4867            // If we have multiple expressions, output them comma-separated
4868            if !into.expressions.is_empty() {
4869                for (i, expr) in into.expressions.iter().enumerate() {
4870                    if i > 0 {
4871                        self.write(", ");
4872                    }
4873                    self.generate_expression(expr)?;
4874                }
4875            } else {
4876                self.generate_expression(&into.this)?;
4877            }
4878        }
4879
4880        // FROM clause
4881        if let Some(from) = &select.from {
4882            if self.config.pretty {
4883                self.write_newline();
4884                self.write_indent();
4885            } else {
4886                self.write_space();
4887            }
4888            self.write_keyword("FROM");
4889            self.write_space();
4890
4891            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4892            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4893            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4894            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4895            let has_tablesample = from
4896                .expressions
4897                .iter()
4898                .any(|e| matches!(e, Expression::TableSample(_)));
4899            let is_cross_join_dialect = matches!(
4900                self.config.dialect,
4901                Some(DialectType::BigQuery)
4902                    | Some(DialectType::Hive)
4903                    | Some(DialectType::Spark)
4904                    | Some(DialectType::Databricks)
4905                    | Some(DialectType::SQLite)
4906                    | Some(DialectType::ClickHouse)
4907            );
4908            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4909            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4910            let source_is_same_as_target = self.config.source_dialect.is_some()
4911                && self.config.source_dialect == self.config.dialect;
4912            let source_is_cross_join_dialect = matches!(
4913                self.config.source_dialect,
4914                Some(DialectType::BigQuery)
4915                    | Some(DialectType::Hive)
4916                    | Some(DialectType::Spark)
4917                    | Some(DialectType::Databricks)
4918                    | Some(DialectType::SQLite)
4919                    | Some(DialectType::ClickHouse)
4920            );
4921            let use_cross_join = !has_tablesample
4922                && is_cross_join_dialect
4923                && (source_is_same_as_target
4924                    || source_is_cross_join_dialect
4925                    || self.config.source_dialect.is_none());
4926
4927            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4928            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4929
4930            for (i, expr) in from.expressions.iter().enumerate() {
4931                if i > 0 {
4932                    if use_cross_join {
4933                        self.write(" CROSS JOIN ");
4934                    } else {
4935                        self.write(", ");
4936                    }
4937                }
4938                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4939                    self.write("(");
4940                    self.generate_expression(expr)?;
4941                    self.write(")");
4942                } else {
4943                    self.generate_expression(expr)?;
4944                }
4945                // Output leading comments that were on the table name before FROM
4946                // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
4947                let leading = Self::extract_table_leading_comments(expr);
4948                for comment in &leading {
4949                    self.write_space();
4950                    self.write_formatted_comment(comment);
4951                }
4952            }
4953        }
4954
4955        // JOINs - handle nested join structure for pretty printing
4956        // Deferred-condition joins "own" the non-deferred joins that follow them
4957        // until the next deferred join or end of list
4958        if self.config.pretty {
4959            self.generate_joins_with_nesting(&select.joins)?;
4960        } else {
4961            for join in &select.joins {
4962                self.generate_join(join)?;
4963            }
4964            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4965            for join in select.joins.iter().rev() {
4966                if join.deferred_condition {
4967                    self.generate_join_condition(join)?;
4968                }
4969            }
4970        }
4971
4972        // LATERAL VIEW clauses (Hive/Spark)
4973        for (lv_idx, lateral_view) in select.lateral_views.iter().enumerate() {
4974            self.generate_lateral_view(lateral_view, lv_idx)?;
4975        }
4976
4977        // PREWHERE (ClickHouse)
4978        if let Some(prewhere) = &select.prewhere {
4979            self.write_clause_condition("PREWHERE", prewhere)?;
4980        }
4981
4982        // WHERE
4983        if let Some(where_clause) = &select.where_clause {
4984            self.write_clause_condition("WHERE", &where_clause.this)?;
4985        }
4986
4987        // CONNECT BY (Oracle hierarchical queries)
4988        if let Some(connect) = &select.connect {
4989            self.generate_connect(connect)?;
4990        }
4991
4992        // GROUP BY
4993        if let Some(group_by) = &select.group_by {
4994            if self.config.pretty {
4995                // Output leading comments on their own lines before GROUP BY
4996                for comment in &group_by.comments {
4997                    self.write_newline();
4998                    self.write_indent();
4999                    self.write_formatted_comment(comment);
5000                }
5001                self.write_newline();
5002                self.write_indent();
5003            } else {
5004                self.write_space();
5005                // In non-pretty mode, output comments inline
5006                for comment in &group_by.comments {
5007                    self.write_formatted_comment(comment);
5008                    self.write_space();
5009                }
5010            }
5011            let clickhouse_bare_modifiers =
5012                matches!(self.config.dialect, Some(DialectType::ClickHouse))
5013                    && group_by.all.is_none()
5014                    && (group_by.totals || !group_by.expressions.is_empty())
5015                    && group_by.expressions.iter().all(|expr| match expr {
5016                        Expression::Cube(c) => c.expressions.is_empty(),
5017                        Expression::Rollup(r) => r.expressions.is_empty(),
5018                        _ => false,
5019                    });
5020
5021            if clickhouse_bare_modifiers {
5022                let trailing_cube = group_by
5023                    .expressions
5024                    .iter()
5025                    .any(|expr| matches!(expr, Expression::Cube(c) if c.expressions.is_empty()));
5026                let trailing_rollup = group_by
5027                    .expressions
5028                    .iter()
5029                    .any(|expr| matches!(expr, Expression::Rollup(r) if r.expressions.is_empty()));
5030
5031                if trailing_cube {
5032                    self.write_keyword("WITH CUBE");
5033                } else if trailing_rollup {
5034                    self.write_keyword("WITH ROLLUP");
5035                }
5036
5037                if group_by.totals {
5038                    if trailing_cube || trailing_rollup {
5039                        self.write_space();
5040                    }
5041                    self.write_keyword("WITH TOTALS");
5042                }
5043            } else {
5044                self.write_keyword("GROUP BY");
5045                // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
5046                match group_by.all {
5047                    Some(true) => {
5048                        self.write_space();
5049                        self.write_keyword("ALL");
5050                    }
5051                    Some(false) => {
5052                        self.write_space();
5053                        self.write_keyword("DISTINCT");
5054                    }
5055                    None => {}
5056                }
5057                if !group_by.expressions.is_empty() {
5058                    // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
5059                    // These are represented as Cube/Rollup expressions with empty expressions at the end
5060                    let mut trailing_cube = false;
5061                    let mut trailing_rollup = false;
5062                    let mut plain_expressions: Vec<&Expression> = Vec::new();
5063                    let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
5064                    let mut cube_expressions: Vec<&Expression> = Vec::new();
5065                    let mut rollup_expressions: Vec<&Expression> = Vec::new();
5066
5067                    for expr in &group_by.expressions {
5068                        match expr {
5069                            Expression::Cube(c) if c.expressions.is_empty() => {
5070                                trailing_cube = true;
5071                            }
5072                            Expression::Rollup(r) if r.expressions.is_empty() => {
5073                                trailing_rollup = true;
5074                            }
5075                            Expression::Function(f) if f.name == "CUBE" => {
5076                                cube_expressions.push(expr);
5077                            }
5078                            Expression::Function(f) if f.name == "ROLLUP" => {
5079                                rollup_expressions.push(expr);
5080                            }
5081                            Expression::Function(f) if f.name == "GROUPING SETS" => {
5082                                grouping_sets_expressions.push(expr);
5083                            }
5084                            _ => {
5085                                plain_expressions.push(expr);
5086                            }
5087                        }
5088                    }
5089
5090                    // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
5091                    let mut regular_expressions: Vec<&Expression> = Vec::new();
5092                    regular_expressions.extend(plain_expressions);
5093                    regular_expressions.extend(grouping_sets_expressions);
5094                    regular_expressions.extend(cube_expressions);
5095                    regular_expressions.extend(rollup_expressions);
5096
5097                    if self.config.pretty {
5098                        self.write_newline();
5099                        self.indent_level += 1;
5100                        self.write_indent();
5101                    } else {
5102                        self.write_space();
5103                    }
5104
5105                    for (i, expr) in regular_expressions.iter().enumerate() {
5106                        if i > 0 {
5107                            if self.config.pretty {
5108                                self.write(",");
5109                                self.write_newline();
5110                                self.write_indent();
5111                            } else {
5112                                self.write(", ");
5113                            }
5114                        }
5115                        self.generate_expression(expr)?;
5116                    }
5117
5118                    if self.config.pretty {
5119                        self.indent_level -= 1;
5120                    }
5121
5122                    // Output trailing WITH CUBE or WITH ROLLUP
5123                    if trailing_cube {
5124                        self.write_space();
5125                        self.write_keyword("WITH CUBE");
5126                    } else if trailing_rollup {
5127                        self.write_space();
5128                        self.write_keyword("WITH ROLLUP");
5129                    }
5130                }
5131
5132                // ClickHouse: WITH TOTALS
5133                if group_by.totals {
5134                    self.write_space();
5135                    self.write_keyword("WITH TOTALS");
5136                }
5137            }
5138        }
5139
5140        // HAVING
5141        if let Some(having) = &select.having {
5142            if self.config.pretty {
5143                // Output leading comments on their own lines before HAVING
5144                for comment in &having.comments {
5145                    self.write_newline();
5146                    self.write_indent();
5147                    self.write_formatted_comment(comment);
5148                }
5149            } else {
5150                for comment in &having.comments {
5151                    self.write_space();
5152                    self.write_formatted_comment(comment);
5153                }
5154            }
5155            self.write_clause_condition("HAVING", &having.this)?;
5156        }
5157
5158        // QUALIFY and WINDOW clause ordering depends on input SQL
5159        if select.qualify_after_window {
5160            // WINDOW before QUALIFY (DuckDB style)
5161            if let Some(windows) = &select.windows {
5162                self.write_window_clause(windows)?;
5163            }
5164            if let Some(qualify) = &select.qualify {
5165                self.write_clause_condition("QUALIFY", &qualify.this)?;
5166            }
5167        } else {
5168            // QUALIFY before WINDOW (Snowflake/BigQuery default)
5169            if let Some(qualify) = &select.qualify {
5170                self.write_clause_condition("QUALIFY", &qualify.this)?;
5171            }
5172            if let Some(windows) = &select.windows {
5173                self.write_window_clause(windows)?;
5174            }
5175        }
5176
5177        // DISTRIBUTE BY (Hive/Spark)
5178        if let Some(distribute_by) = &select.distribute_by {
5179            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
5180        }
5181
5182        // CLUSTER BY (Hive/Spark)
5183        if let Some(cluster_by) = &select.cluster_by {
5184            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
5185        }
5186
5187        // SORT BY (Hive/Spark - comes before ORDER BY)
5188        if let Some(sort_by) = &select.sort_by {
5189            self.write_order_clause("SORT BY", &sort_by.expressions)?;
5190        }
5191
5192        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
5193        if let Some(order_by) = &select.order_by {
5194            if self.config.pretty {
5195                // Output leading comments on their own lines before ORDER BY
5196                for comment in &order_by.comments {
5197                    self.write_newline();
5198                    self.write_indent();
5199                    self.write_formatted_comment(comment);
5200                }
5201            } else {
5202                for comment in &order_by.comments {
5203                    self.write_space();
5204                    self.write_formatted_comment(comment);
5205                }
5206            }
5207            let keyword = if order_by.siblings {
5208                "ORDER SIBLINGS BY"
5209            } else {
5210                "ORDER BY"
5211            };
5212            self.write_order_clause(keyword, &order_by.expressions)?;
5213        }
5214
5215        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
5216        if select.order_by.is_none()
5217            && select.fetch.is_some()
5218            && matches!(
5219                self.config.dialect,
5220                Some(DialectType::TSQL) | Some(DialectType::Fabric)
5221            )
5222        {
5223            if self.config.pretty {
5224                self.write_newline();
5225                self.write_indent();
5226            } else {
5227                self.write_space();
5228            }
5229            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
5230        }
5231
5232        // LIMIT and OFFSET
5233        // PostgreSQL and others use: LIMIT count OFFSET offset
5234        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
5235        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
5236        let is_presto_like = matches!(
5237            self.config.dialect,
5238            Some(DialectType::Presto) | Some(DialectType::Trino)
5239        );
5240
5241        if is_presto_like && select.offset.is_some() {
5242            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
5243            if let Some(offset) = &select.offset {
5244                if self.config.pretty {
5245                    self.write_newline();
5246                    self.write_indent();
5247                } else {
5248                    self.write_space();
5249                }
5250                self.write_keyword("OFFSET");
5251                self.write_space();
5252                self.write_limit_expr(&offset.this)?;
5253                if offset.rows == Some(true) {
5254                    self.write_space();
5255                    self.write_keyword("ROWS");
5256                }
5257            }
5258            if let Some(limit) = &select.limit {
5259                if self.config.pretty {
5260                    self.write_newline();
5261                    self.write_indent();
5262                } else {
5263                    self.write_space();
5264                }
5265                self.write_keyword("LIMIT");
5266                self.write_space();
5267                self.write_limit_expr(&limit.this)?;
5268                if limit.percent {
5269                    self.write_space();
5270                    self.write_keyword("PERCENT");
5271                }
5272                // Emit any comments that were captured from before the LIMIT keyword
5273                for comment in &limit.comments {
5274                    self.write(" ");
5275                    self.write_formatted_comment(comment);
5276                }
5277            }
5278        } else {
5279            // Check if FETCH will be converted to LIMIT (used for ordering)
5280            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
5281                !fetch.percent
5282                    && !fetch.with_ties
5283                    && fetch.count.is_some()
5284                    && matches!(
5285                        self.config.dialect,
5286                        Some(DialectType::Spark)
5287                            | Some(DialectType::Hive)
5288                            | Some(DialectType::DuckDB)
5289                            | Some(DialectType::SQLite)
5290                            | Some(DialectType::MySQL)
5291                            | Some(DialectType::BigQuery)
5292                            | Some(DialectType::Databricks)
5293                            | Some(DialectType::StarRocks)
5294                            | Some(DialectType::Doris)
5295                            | Some(DialectType::Athena)
5296                            | Some(DialectType::ClickHouse)
5297                            | Some(DialectType::Redshift)
5298                    )
5299            });
5300
5301            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
5302            if let Some(limit) = &select.limit {
5303                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
5304                if !matches!(
5305                    self.config.dialect,
5306                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
5307                ) {
5308                    if self.config.pretty {
5309                        self.write_newline();
5310                        self.write_indent();
5311                    } else {
5312                        self.write_space();
5313                    }
5314                    self.write_keyword("LIMIT");
5315                    self.write_space();
5316                    self.write_limit_expr(&limit.this)?;
5317                    if limit.percent {
5318                        self.write_space();
5319                        self.write_keyword("PERCENT");
5320                    }
5321                    // Emit any comments that were captured from before the LIMIT keyword
5322                    for comment in &limit.comments {
5323                        self.write(" ");
5324                        self.write_formatted_comment(comment);
5325                    }
5326                }
5327            }
5328
5329            // Convert TOP to LIMIT for non-TOP dialects
5330            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
5331                if let Some(top) = &select.top {
5332                    if !top.percent && !top.with_ties {
5333                        if self.config.pretty {
5334                            self.write_newline();
5335                            self.write_indent();
5336                        } else {
5337                            self.write_space();
5338                        }
5339                        self.write_keyword("LIMIT");
5340                        self.write_space();
5341                        self.generate_expression(&top.this)?;
5342                    }
5343                }
5344            }
5345
5346            // If FETCH will be converted to LIMIT and there's also OFFSET,
5347            // emit LIMIT from FETCH BEFORE the OFFSET
5348            if fetch_as_limit && select.offset.is_some() {
5349                if let Some(fetch) = &select.fetch {
5350                    if self.config.pretty {
5351                        self.write_newline();
5352                        self.write_indent();
5353                    } else {
5354                        self.write_space();
5355                    }
5356                    self.write_keyword("LIMIT");
5357                    self.write_space();
5358                    self.generate_expression(fetch.count.as_ref().unwrap())?;
5359                }
5360            }
5361
5362            // OFFSET
5363            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
5364            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
5365            if let Some(offset) = &select.offset {
5366                if self.config.pretty {
5367                    self.write_newline();
5368                    self.write_indent();
5369                } else {
5370                    self.write_space();
5371                }
5372                if matches!(
5373                    self.config.dialect,
5374                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
5375                ) {
5376                    // SQL Server 2012+ OFFSET ... FETCH syntax
5377                    self.write_keyword("OFFSET");
5378                    self.write_space();
5379                    self.write_limit_expr(&offset.this)?;
5380                    self.write_space();
5381                    self.write_keyword("ROWS");
5382                    // If there was a real LIMIT, use FETCH NEXT ... ROWS ONLY.
5383                    // PostgreSQL LIMIT NULL / LIMIT ALL mean "no limit", so
5384                    // T-SQL/Fabric should keep the OFFSET without FETCH.
5385                    if let Some(limit) = &select.limit {
5386                        if !Self::is_noop_limit_expr(&limit.this) {
5387                            self.write_space();
5388                            self.write_keyword("FETCH NEXT");
5389                            self.write_space();
5390                            self.write_limit_expr(&limit.this)?;
5391                            self.write_space();
5392                            self.write_keyword("ROWS ONLY");
5393                        }
5394                    }
5395                } else {
5396                    self.write_keyword("OFFSET");
5397                    self.write_space();
5398                    self.write_limit_expr(&offset.this)?;
5399                    // Output ROWS keyword if it was in the original SQL
5400                    if offset.rows == Some(true) {
5401                        self.write_space();
5402                        self.write_keyword("ROWS");
5403                    }
5404                }
5405            }
5406        }
5407
5408        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
5409        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5410            if let Some(limit_by) = &select.limit_by {
5411                if !limit_by.is_empty() {
5412                    self.write_space();
5413                    self.write_keyword("BY");
5414                    self.write_space();
5415                    for (i, expr) in limit_by.iter().enumerate() {
5416                        if i > 0 {
5417                            self.write(", ");
5418                        }
5419                        self.generate_expression(expr)?;
5420                    }
5421                }
5422            }
5423        }
5424
5425        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
5426        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5427            if let Some(settings) = &select.settings {
5428                if self.config.pretty {
5429                    self.write_newline();
5430                    self.write_indent();
5431                } else {
5432                    self.write_space();
5433                }
5434                self.write_keyword("SETTINGS");
5435                self.write_space();
5436                for (i, expr) in settings.iter().enumerate() {
5437                    if i > 0 {
5438                        self.write(", ");
5439                    }
5440                    self.generate_expression(expr)?;
5441                }
5442            }
5443
5444            if let Some(format_expr) = &select.format {
5445                if self.config.pretty {
5446                    self.write_newline();
5447                    self.write_indent();
5448                } else {
5449                    self.write_space();
5450                }
5451                self.write_keyword("FORMAT");
5452                self.write_space();
5453                self.generate_expression(format_expr)?;
5454            }
5455        }
5456
5457        // FETCH FIRST/NEXT
5458        if let Some(fetch) = &select.fetch {
5459            // Check if we already emitted LIMIT from FETCH before OFFSET
5460            let fetch_already_as_limit = select.offset.is_some()
5461                && !fetch.percent
5462                && !fetch.with_ties
5463                && fetch.count.is_some()
5464                && matches!(
5465                    self.config.dialect,
5466                    Some(DialectType::Spark)
5467                        | Some(DialectType::Hive)
5468                        | Some(DialectType::DuckDB)
5469                        | Some(DialectType::SQLite)
5470                        | Some(DialectType::MySQL)
5471                        | Some(DialectType::BigQuery)
5472                        | Some(DialectType::Databricks)
5473                        | Some(DialectType::StarRocks)
5474                        | Some(DialectType::Doris)
5475                        | Some(DialectType::Athena)
5476                        | Some(DialectType::ClickHouse)
5477                        | Some(DialectType::Redshift)
5478                );
5479
5480            if fetch_already_as_limit {
5481                // Already emitted as LIMIT before OFFSET, skip
5482            } else {
5483                if self.config.pretty {
5484                    self.write_newline();
5485                    self.write_indent();
5486                } else {
5487                    self.write_space();
5488                }
5489
5490                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
5491                let use_limit = !fetch.percent
5492                    && !fetch.with_ties
5493                    && fetch.count.is_some()
5494                    && matches!(
5495                        self.config.dialect,
5496                        Some(DialectType::Spark)
5497                            | Some(DialectType::Hive)
5498                            | Some(DialectType::DuckDB)
5499                            | Some(DialectType::SQLite)
5500                            | Some(DialectType::MySQL)
5501                            | Some(DialectType::BigQuery)
5502                            | Some(DialectType::Databricks)
5503                            | Some(DialectType::StarRocks)
5504                            | Some(DialectType::Doris)
5505                            | Some(DialectType::Athena)
5506                            | Some(DialectType::ClickHouse)
5507                            | Some(DialectType::Redshift)
5508                    );
5509
5510                if use_limit {
5511                    self.write_keyword("LIMIT");
5512                    self.write_space();
5513                    self.generate_expression(fetch.count.as_ref().unwrap())?;
5514                } else {
5515                    self.write_keyword("FETCH");
5516                    self.write_space();
5517                    self.write_keyword(&fetch.direction);
5518                    if let Some(ref count) = fetch.count {
5519                        self.write_space();
5520                        self.generate_expression(count)?;
5521                    }
5522                    if fetch.percent {
5523                        self.write_space();
5524                        self.write_keyword("PERCENT");
5525                    }
5526                    if fetch.rows {
5527                        self.write_space();
5528                        self.write_keyword("ROWS");
5529                    }
5530                    if fetch.with_ties {
5531                        self.write_space();
5532                        self.write_keyword("WITH TIES");
5533                    } else {
5534                        self.write_space();
5535                        self.write_keyword("ONLY");
5536                    }
5537                }
5538            } // close fetch_already_as_limit else
5539        }
5540
5541        // SAMPLE / TABLESAMPLE
5542        if let Some(sample) = &select.sample {
5543            use crate::dialects::DialectType;
5544            if self.config.pretty {
5545                self.write_newline();
5546            } else {
5547                self.write_space();
5548            }
5549
5550            if sample.is_using_sample {
5551                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
5552                self.write_keyword("USING SAMPLE");
5553                self.generate_sample_body(sample)?;
5554            } else {
5555                self.write_keyword("TABLESAMPLE");
5556
5557                // Snowflake defaults to BERNOULLI when no explicit method is given
5558                let snowflake_bernoulli =
5559                    matches!(self.config.dialect, Some(DialectType::Snowflake))
5560                        && !sample.explicit_method;
5561                if snowflake_bernoulli {
5562                    self.write_space();
5563                    self.write_keyword("BERNOULLI");
5564                }
5565
5566                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
5567                if matches!(sample.method, SampleMethod::Bucket) {
5568                    self.write_space();
5569                    self.write("(");
5570                    self.write_keyword("BUCKET");
5571                    self.write_space();
5572                    if let Some(ref num) = sample.bucket_numerator {
5573                        self.generate_expression(num)?;
5574                    }
5575                    self.write_space();
5576                    self.write_keyword("OUT OF");
5577                    self.write_space();
5578                    if let Some(ref denom) = sample.bucket_denominator {
5579                        self.generate_expression(denom)?;
5580                    }
5581                    if let Some(ref field) = sample.bucket_field {
5582                        self.write_space();
5583                        self.write_keyword("ON");
5584                        self.write_space();
5585                        self.generate_expression(field)?;
5586                    }
5587                    self.write(")");
5588                } else if sample.unit_after_size {
5589                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
5590                    if sample.explicit_method && sample.method_before_size {
5591                        self.write_space();
5592                        match sample.method {
5593                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5594                            SampleMethod::System => self.write_keyword("SYSTEM"),
5595                            SampleMethod::Block => self.write_keyword("BLOCK"),
5596                            SampleMethod::Row => self.write_keyword("ROW"),
5597                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5598                            _ => {}
5599                        }
5600                    }
5601                    self.write(" (");
5602                    self.generate_expression(&sample.size)?;
5603                    self.write_space();
5604                    match sample.method {
5605                        SampleMethod::Percent => self.write_keyword("PERCENT"),
5606                        SampleMethod::Row => self.write_keyword("ROWS"),
5607                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
5608                        _ => {
5609                            self.write_keyword("PERCENT");
5610                        }
5611                    }
5612                    self.write(")");
5613                } else {
5614                    // Syntax: TABLESAMPLE METHOD (size)
5615                    self.write_space();
5616                    match sample.method {
5617                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5618                        SampleMethod::System => self.write_keyword("SYSTEM"),
5619                        SampleMethod::Block => self.write_keyword("BLOCK"),
5620                        SampleMethod::Row => self.write_keyword("ROW"),
5621                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
5622                        SampleMethod::Bucket => {}
5623                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5624                    }
5625                    self.write(" (");
5626                    self.generate_expression(&sample.size)?;
5627                    if matches!(sample.method, SampleMethod::Percent) {
5628                        self.write_space();
5629                        self.write_keyword("PERCENT");
5630                    }
5631                    self.write(")");
5632                }
5633            }
5634
5635            if let Some(seed) = &sample.seed {
5636                self.write_space();
5637                // Databricks/Spark use REPEATABLE, not SEED
5638                let use_seed = sample.use_seed_keyword
5639                    && !matches!(
5640                        self.config.dialect,
5641                        Some(crate::dialects::DialectType::Databricks)
5642                            | Some(crate::dialects::DialectType::Spark)
5643                    );
5644                if use_seed {
5645                    self.write_keyword("SEED");
5646                } else {
5647                    self.write_keyword("REPEATABLE");
5648                }
5649                self.write(" (");
5650                self.generate_expression(seed)?;
5651                self.write(")");
5652            }
5653        }
5654
5655        // FOR UPDATE/SHARE locks
5656        // Skip locking clauses for dialects that don't support them
5657        if self.config.locking_reads_supported {
5658            for lock in &select.locks {
5659                if self.config.pretty {
5660                    self.write_newline();
5661                    self.write_indent();
5662                } else {
5663                    self.write_space();
5664                }
5665                self.generate_lock(lock)?;
5666            }
5667        }
5668
5669        // FOR XML clause (T-SQL)
5670        if !select.for_xml.is_empty() {
5671            if self.config.pretty {
5672                self.write_newline();
5673                self.write_indent();
5674            } else {
5675                self.write_space();
5676            }
5677            self.write_keyword("FOR XML");
5678            for (i, opt) in select.for_xml.iter().enumerate() {
5679                if self.config.pretty {
5680                    if i > 0 {
5681                        self.write(",");
5682                    }
5683                    self.write_newline();
5684                    self.write_indent();
5685                    self.write("  "); // extra indent for options
5686                } else {
5687                    if i > 0 {
5688                        self.write(",");
5689                    }
5690                    self.write_space();
5691                }
5692                self.generate_for_xml_option(opt)?;
5693            }
5694        }
5695
5696        // FOR JSON clause (T-SQL)
5697        if !select.for_json.is_empty()
5698            && matches!(
5699                self.config.dialect,
5700                None | Some(DialectType::TSQL) | Some(DialectType::Fabric)
5701            )
5702        {
5703            if self.config.pretty {
5704                self.write_newline();
5705                self.write_indent();
5706            } else {
5707                self.write_space();
5708            }
5709            self.write_keyword("FOR JSON");
5710            for (i, opt) in select.for_json.iter().enumerate() {
5711                if self.config.pretty {
5712                    if i > 0 {
5713                        self.write(",");
5714                    }
5715                    self.write_newline();
5716                    self.write_indent();
5717                    self.write("  "); // extra indent for options
5718                } else {
5719                    if i > 0 {
5720                        self.write(",");
5721                    }
5722                    self.write_space();
5723                }
5724                self.generate_for_xml_option(opt)?;
5725            }
5726        }
5727
5728        // TSQL: OPTION clause
5729        if let Some(ref option) = select.option {
5730            if matches!(
5731                self.config.dialect,
5732                Some(crate::dialects::DialectType::TSQL)
5733                    | Some(crate::dialects::DialectType::Fabric)
5734            ) {
5735                self.write_space();
5736                self.write(option);
5737            }
5738        }
5739
5740        Ok(())
5741    }
5742
5743    /// Generate a single FOR XML option
5744    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5745        match opt {
5746            Expression::QueryOption(qo) => {
5747                // Extract the option name from Var
5748                if let Expression::Var(var) = &*qo.this {
5749                    self.write(&var.this);
5750                } else {
5751                    self.generate_expression(&qo.this)?;
5752                }
5753                // If there's an expression (like PATH('element')), output it in parens
5754                if let Some(expr) = &qo.expression {
5755                    self.write("(");
5756                    self.generate_expression(expr)?;
5757                    self.write(")");
5758                }
5759            }
5760            _ => {
5761                self.generate_expression(opt)?;
5762            }
5763        }
5764        Ok(())
5765    }
5766
5767    fn generate_with(&mut self, with: &With) -> Result<()> {
5768        use crate::dialects::DialectType;
5769
5770        // Output leading comments before WITH
5771        for comment in &with.leading_comments {
5772            self.write_formatted_comment(comment);
5773            self.write(" ");
5774        }
5775        self.write_keyword("WITH");
5776        if with.recursive && self.config.cte_recursive_keyword_required {
5777            self.write_space();
5778            self.write_keyword("RECURSIVE");
5779        }
5780        self.write_space();
5781
5782        // BigQuery doesn't support column aliases in CTE definitions
5783        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5784
5785        for (i, cte) in with.ctes.iter().enumerate() {
5786            if i > 0 {
5787                self.write(",");
5788                if self.config.pretty {
5789                    self.write_space();
5790                } else {
5791                    self.write(" ");
5792                }
5793            }
5794            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5795                self.generate_expression(&cte.this)?;
5796                self.write_space();
5797                self.write_keyword("AS");
5798                self.write_space();
5799                self.generate_identifier(&cte.alias)?;
5800                continue;
5801            }
5802            self.generate_identifier(&cte.alias)?;
5803            // Output CTE comments after alias name, before AS
5804            for comment in &cte.comments {
5805                self.write_space();
5806                self.write_formatted_comment(comment);
5807            }
5808            if !cte.columns.is_empty() && !skip_cte_columns {
5809                self.write("(");
5810                for (j, col) in cte.columns.iter().enumerate() {
5811                    if j > 0 {
5812                        self.write(", ");
5813                    }
5814                    self.generate_identifier(col)?;
5815                }
5816                self.write(")");
5817            }
5818            // USING KEY (columns) for DuckDB recursive CTEs
5819            if !cte.key_expressions.is_empty() {
5820                self.write_space();
5821                self.write_keyword("USING KEY");
5822                self.write(" (");
5823                for (i, key) in cte.key_expressions.iter().enumerate() {
5824                    if i > 0 {
5825                        self.write(", ");
5826                    }
5827                    self.generate_identifier(key)?;
5828                }
5829                self.write(")");
5830            }
5831            self.write_space();
5832            self.write_keyword("AS");
5833            // MATERIALIZED / NOT MATERIALIZED
5834            if let Some(materialized) = cte.materialized {
5835                self.write_space();
5836                if materialized {
5837                    self.write_keyword("MATERIALIZED");
5838                } else {
5839                    self.write_keyword("NOT MATERIALIZED");
5840                }
5841            }
5842            self.write(" (");
5843            if self.config.pretty {
5844                self.write_newline();
5845                self.indent_level += 1;
5846                self.write_indent();
5847            }
5848            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5849            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5850            let wrap_values_in_select = matches!(
5851                self.config.dialect,
5852                Some(DialectType::Spark) | Some(DialectType::Databricks)
5853            ) && matches!(&cte.this, Expression::Values(_));
5854
5855            if wrap_values_in_select {
5856                self.write_keyword("SELECT");
5857                self.write(" * ");
5858                self.write_keyword("FROM");
5859                self.write_space();
5860            }
5861            self.generate_expression(&cte.this)?;
5862            if self.config.pretty {
5863                self.write_newline();
5864                self.indent_level -= 1;
5865                self.write_indent();
5866            }
5867            self.write(")");
5868        }
5869
5870        // Generate SEARCH/CYCLE clause if present
5871        if let Some(search) = &with.search {
5872            self.write_space();
5873            self.generate_expression(search)?;
5874        }
5875
5876        Ok(())
5877    }
5878
5879    /// Generate joins with proper nesting structure for pretty printing.
5880    /// Deferred-condition joins "own" the non-deferred joins that follow them
5881    /// within the same nesting_group.
5882    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5883        let mut i = 0;
5884        while i < joins.len() {
5885            if joins[i].deferred_condition {
5886                let parent_group = joins[i].nesting_group;
5887
5888                // This join owns the following non-deferred joins in the same nesting_group
5889                // First output the join keyword and table (without condition)
5890                self.generate_join_without_condition(&joins[i])?;
5891
5892                // Find the range of child joins: same nesting_group and not deferred
5893                let child_start = i + 1;
5894                let mut child_end = child_start;
5895                while child_end < joins.len()
5896                    && !joins[child_end].deferred_condition
5897                    && joins[child_end].nesting_group == parent_group
5898                {
5899                    child_end += 1;
5900                }
5901
5902                // Output child joins with extra indentation
5903                if child_start < child_end {
5904                    self.indent_level += 1;
5905                    for j in child_start..child_end {
5906                        self.generate_join(&joins[j])?;
5907                    }
5908                    self.indent_level -= 1;
5909                }
5910
5911                // Output the deferred condition at the parent level
5912                self.generate_join_condition(&joins[i])?;
5913
5914                i = child_end;
5915            } else {
5916                // Regular join (no nesting)
5917                self.generate_join(&joins[i])?;
5918                i += 1;
5919            }
5920        }
5921        Ok(())
5922    }
5923
5924    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5925    /// Used for deferred-condition joins where the condition is output after child joins.
5926    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5927        // Save and temporarily clear the condition to prevent generate_join from outputting it
5928        // We achieve this by creating a modified copy
5929        let mut join_copy = join.clone();
5930        join_copy.on = None;
5931        join_copy.using = Vec::new();
5932        join_copy.deferred_condition = false;
5933        self.generate_join(&join_copy)
5934    }
5935
5936    fn generate_join(&mut self, join: &Join) -> Result<()> {
5937        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5938        if join.kind == JoinKind::Implicit {
5939            self.write(",");
5940            if self.config.pretty {
5941                self.write_newline();
5942                self.write_indent();
5943            } else {
5944                self.write_space();
5945            }
5946            self.generate_expression(&join.this)?;
5947            return Ok(());
5948        }
5949
5950        if self.config.pretty {
5951            self.write_newline();
5952            self.write_indent();
5953        } else {
5954            self.write_space();
5955        }
5956
5957        // Helper: format hint suffix (e.g., " LOOP" or "")
5958        // Only include join hints for dialects that support them
5959        let hint_str = if self.config.join_hints {
5960            join.join_hint
5961                .as_ref()
5962                .map(|h| format!(" {}", h))
5963                .unwrap_or_default()
5964        } else {
5965            String::new()
5966        };
5967
5968        let clickhouse_join_keyword =
5969            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5970                if let Some(hint) = &join.join_hint {
5971                    let mut global = false;
5972                    let mut strictness: Option<&'static str> = None;
5973                    for part in hint.split_whitespace() {
5974                        if part.eq_ignore_ascii_case("GLOBAL") {
5975                            global = true;
5976                        } else if part.eq_ignore_ascii_case("ALL") {
5977                            strictness = Some("ALL");
5978                        } else if part.eq_ignore_ascii_case("ANY") {
5979                            strictness = Some("ANY");
5980                        } else if part.eq_ignore_ascii_case("ASOF") {
5981                            strictness = Some("ASOF");
5982                        } else if part.eq_ignore_ascii_case("SEMI") {
5983                            strictness = Some("SEMI");
5984                        } else if part.eq_ignore_ascii_case("ANTI") {
5985                            strictness = Some("ANTI");
5986                        }
5987                    }
5988
5989                    if global || strictness.is_some() {
5990                        let join_type = match join.kind {
5991                            JoinKind::Left => {
5992                                if join.use_outer_keyword {
5993                                    "LEFT OUTER"
5994                                } else if join.use_inner_keyword {
5995                                    "LEFT INNER"
5996                                } else {
5997                                    "LEFT"
5998                                }
5999                            }
6000                            JoinKind::Right => {
6001                                if join.use_outer_keyword {
6002                                    "RIGHT OUTER"
6003                                } else if join.use_inner_keyword {
6004                                    "RIGHT INNER"
6005                                } else {
6006                                    "RIGHT"
6007                                }
6008                            }
6009                            JoinKind::Full => {
6010                                if join.use_outer_keyword {
6011                                    "FULL OUTER"
6012                                } else {
6013                                    "FULL"
6014                                }
6015                            }
6016                            JoinKind::Inner => {
6017                                if join.use_inner_keyword {
6018                                    "INNER"
6019                                } else {
6020                                    ""
6021                                }
6022                            }
6023                            _ => "",
6024                        };
6025
6026                        let mut parts = Vec::new();
6027                        if global {
6028                            parts.push("GLOBAL");
6029                        }
6030                        if !join_type.is_empty() {
6031                            parts.push(join_type);
6032                        }
6033                        if let Some(strict) = strictness {
6034                            parts.push(strict);
6035                        }
6036                        parts.push("JOIN");
6037                        Some(parts.join(" "))
6038                    } else {
6039                        None
6040                    }
6041                } else {
6042                    None
6043                }
6044            } else {
6045                None
6046            };
6047
6048        // Output any comments associated with this join
6049        // In pretty mode, comments go on their own line before the join keyword
6050        // In non-pretty mode, comments go inline before the join keyword
6051        if !join.comments.is_empty() {
6052            if self.config.pretty {
6053                // In pretty mode, go back before the newline+indent we just wrote
6054                // and output comments on their own lines
6055                // We need to output comments BEFORE the join keyword on separate lines
6056                // Trim the trailing newline+indent we already wrote
6057                let trimmed = self.output.trim_end().len();
6058                self.output.truncate(trimmed);
6059                for comment in &join.comments {
6060                    self.write_newline();
6061                    self.write_indent();
6062                    self.write_formatted_comment(comment);
6063                }
6064                self.write_newline();
6065                self.write_indent();
6066            } else {
6067                for comment in &join.comments {
6068                    self.write_formatted_comment(comment);
6069                    self.write_space();
6070                }
6071            }
6072        }
6073
6074        let directed_str = if join.directed { " DIRECTED" } else { "" };
6075
6076        if let Some(keyword) = clickhouse_join_keyword {
6077            self.write_keyword(&keyword);
6078        } else {
6079            match join.kind {
6080                JoinKind::Inner => {
6081                    if join.use_inner_keyword {
6082                        if hint_str.is_empty() && directed_str.is_empty() {
6083                            self.write_keyword("INNER JOIN");
6084                        } else {
6085                            self.write_keyword("INNER");
6086                            if !hint_str.is_empty() {
6087                                self.write_keyword(&hint_str);
6088                            }
6089                            if !directed_str.is_empty() {
6090                                self.write_keyword(directed_str);
6091                            }
6092                            self.write_keyword(" JOIN");
6093                        }
6094                    } else {
6095                        if !hint_str.is_empty() {
6096                            self.write_keyword(hint_str.trim());
6097                            self.write_keyword(" ");
6098                        }
6099                        if !directed_str.is_empty() {
6100                            self.write_keyword("DIRECTED ");
6101                        }
6102                        self.write_keyword("JOIN");
6103                    }
6104                }
6105                JoinKind::Left => {
6106                    if join.use_outer_keyword {
6107                        if hint_str.is_empty() && directed_str.is_empty() {
6108                            self.write_keyword("LEFT OUTER JOIN");
6109                        } else {
6110                            self.write_keyword("LEFT OUTER");
6111                            if !hint_str.is_empty() {
6112                                self.write_keyword(&hint_str);
6113                            }
6114                            if !directed_str.is_empty() {
6115                                self.write_keyword(directed_str);
6116                            }
6117                            self.write_keyword(" JOIN");
6118                        }
6119                    } else if join.use_inner_keyword {
6120                        if hint_str.is_empty() && directed_str.is_empty() {
6121                            self.write_keyword("LEFT INNER JOIN");
6122                        } else {
6123                            self.write_keyword("LEFT INNER");
6124                            if !hint_str.is_empty() {
6125                                self.write_keyword(&hint_str);
6126                            }
6127                            if !directed_str.is_empty() {
6128                                self.write_keyword(directed_str);
6129                            }
6130                            self.write_keyword(" JOIN");
6131                        }
6132                    } else {
6133                        if hint_str.is_empty() && directed_str.is_empty() {
6134                            self.write_keyword("LEFT JOIN");
6135                        } else {
6136                            self.write_keyword("LEFT");
6137                            if !hint_str.is_empty() {
6138                                self.write_keyword(&hint_str);
6139                            }
6140                            if !directed_str.is_empty() {
6141                                self.write_keyword(directed_str);
6142                            }
6143                            self.write_keyword(" JOIN");
6144                        }
6145                    }
6146                }
6147                JoinKind::Right => {
6148                    if join.use_outer_keyword {
6149                        if hint_str.is_empty() && directed_str.is_empty() {
6150                            self.write_keyword("RIGHT OUTER JOIN");
6151                        } else {
6152                            self.write_keyword("RIGHT OUTER");
6153                            if !hint_str.is_empty() {
6154                                self.write_keyword(&hint_str);
6155                            }
6156                            if !directed_str.is_empty() {
6157                                self.write_keyword(directed_str);
6158                            }
6159                            self.write_keyword(" JOIN");
6160                        }
6161                    } else if join.use_inner_keyword {
6162                        if hint_str.is_empty() && directed_str.is_empty() {
6163                            self.write_keyword("RIGHT INNER JOIN");
6164                        } else {
6165                            self.write_keyword("RIGHT INNER");
6166                            if !hint_str.is_empty() {
6167                                self.write_keyword(&hint_str);
6168                            }
6169                            if !directed_str.is_empty() {
6170                                self.write_keyword(directed_str);
6171                            }
6172                            self.write_keyword(" JOIN");
6173                        }
6174                    } else {
6175                        if hint_str.is_empty() && directed_str.is_empty() {
6176                            self.write_keyword("RIGHT JOIN");
6177                        } else {
6178                            self.write_keyword("RIGHT");
6179                            if !hint_str.is_empty() {
6180                                self.write_keyword(&hint_str);
6181                            }
6182                            if !directed_str.is_empty() {
6183                                self.write_keyword(directed_str);
6184                            }
6185                            self.write_keyword(" JOIN");
6186                        }
6187                    }
6188                }
6189                JoinKind::Full => {
6190                    if join.use_outer_keyword {
6191                        if hint_str.is_empty() && directed_str.is_empty() {
6192                            self.write_keyword("FULL OUTER JOIN");
6193                        } else {
6194                            self.write_keyword("FULL OUTER");
6195                            if !hint_str.is_empty() {
6196                                self.write_keyword(&hint_str);
6197                            }
6198                            if !directed_str.is_empty() {
6199                                self.write_keyword(directed_str);
6200                            }
6201                            self.write_keyword(" JOIN");
6202                        }
6203                    } else {
6204                        if hint_str.is_empty() && directed_str.is_empty() {
6205                            self.write_keyword("FULL JOIN");
6206                        } else {
6207                            self.write_keyword("FULL");
6208                            if !hint_str.is_empty() {
6209                                self.write_keyword(&hint_str);
6210                            }
6211                            if !directed_str.is_empty() {
6212                                self.write_keyword(directed_str);
6213                            }
6214                            self.write_keyword(" JOIN");
6215                        }
6216                    }
6217                }
6218                JoinKind::Outer => {
6219                    if directed_str.is_empty() {
6220                        self.write_keyword("OUTER JOIN");
6221                    } else {
6222                        self.write_keyword("OUTER");
6223                        self.write_keyword(directed_str);
6224                        self.write_keyword(" JOIN");
6225                    }
6226                }
6227                JoinKind::Cross => {
6228                    if directed_str.is_empty() {
6229                        self.write_keyword("CROSS JOIN");
6230                    } else {
6231                        self.write_keyword("CROSS");
6232                        self.write_keyword(directed_str);
6233                        self.write_keyword(" JOIN");
6234                    }
6235                }
6236                JoinKind::Natural => {
6237                    if join.use_inner_keyword {
6238                        if directed_str.is_empty() {
6239                            self.write_keyword("NATURAL INNER JOIN");
6240                        } else {
6241                            self.write_keyword("NATURAL INNER");
6242                            self.write_keyword(directed_str);
6243                            self.write_keyword(" JOIN");
6244                        }
6245                    } else {
6246                        if directed_str.is_empty() {
6247                            self.write_keyword("NATURAL JOIN");
6248                        } else {
6249                            self.write_keyword("NATURAL");
6250                            self.write_keyword(directed_str);
6251                            self.write_keyword(" JOIN");
6252                        }
6253                    }
6254                }
6255                JoinKind::NaturalLeft => {
6256                    if join.use_outer_keyword {
6257                        if directed_str.is_empty() {
6258                            self.write_keyword("NATURAL LEFT OUTER JOIN");
6259                        } else {
6260                            self.write_keyword("NATURAL LEFT OUTER");
6261                            self.write_keyword(directed_str);
6262                            self.write_keyword(" JOIN");
6263                        }
6264                    } else {
6265                        if directed_str.is_empty() {
6266                            self.write_keyword("NATURAL LEFT JOIN");
6267                        } else {
6268                            self.write_keyword("NATURAL LEFT");
6269                            self.write_keyword(directed_str);
6270                            self.write_keyword(" JOIN");
6271                        }
6272                    }
6273                }
6274                JoinKind::NaturalRight => {
6275                    if join.use_outer_keyword {
6276                        if directed_str.is_empty() {
6277                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
6278                        } else {
6279                            self.write_keyword("NATURAL RIGHT OUTER");
6280                            self.write_keyword(directed_str);
6281                            self.write_keyword(" JOIN");
6282                        }
6283                    } else {
6284                        if directed_str.is_empty() {
6285                            self.write_keyword("NATURAL RIGHT JOIN");
6286                        } else {
6287                            self.write_keyword("NATURAL RIGHT");
6288                            self.write_keyword(directed_str);
6289                            self.write_keyword(" JOIN");
6290                        }
6291                    }
6292                }
6293                JoinKind::NaturalFull => {
6294                    if join.use_outer_keyword {
6295                        if directed_str.is_empty() {
6296                            self.write_keyword("NATURAL FULL OUTER JOIN");
6297                        } else {
6298                            self.write_keyword("NATURAL FULL OUTER");
6299                            self.write_keyword(directed_str);
6300                            self.write_keyword(" JOIN");
6301                        }
6302                    } else {
6303                        if directed_str.is_empty() {
6304                            self.write_keyword("NATURAL FULL JOIN");
6305                        } else {
6306                            self.write_keyword("NATURAL FULL");
6307                            self.write_keyword(directed_str);
6308                            self.write_keyword(" JOIN");
6309                        }
6310                    }
6311                }
6312                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
6313                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
6314                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
6315                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
6316                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
6317                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
6318                JoinKind::CrossApply => {
6319                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL-like dialects
6320                    if matches!(
6321                        self.config.dialect,
6322                        Some(DialectType::TSQL) | Some(DialectType::Fabric) | None
6323                    ) {
6324                        self.write_keyword("CROSS APPLY");
6325                    } else {
6326                        self.write_keyword("INNER JOIN LATERAL");
6327                    }
6328                }
6329                JoinKind::OuterApply => {
6330                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL-like dialects
6331                    if matches!(
6332                        self.config.dialect,
6333                        Some(DialectType::TSQL) | Some(DialectType::Fabric) | None
6334                    ) {
6335                        self.write_keyword("OUTER APPLY");
6336                    } else {
6337                        self.write_keyword("LEFT JOIN LATERAL");
6338                    }
6339                }
6340                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
6341                JoinKind::AsOfLeft => {
6342                    if join.use_outer_keyword {
6343                        self.write_keyword("ASOF LEFT OUTER JOIN");
6344                    } else {
6345                        self.write_keyword("ASOF LEFT JOIN");
6346                    }
6347                }
6348                JoinKind::AsOfRight => {
6349                    if join.use_outer_keyword {
6350                        self.write_keyword("ASOF RIGHT OUTER JOIN");
6351                    } else {
6352                        self.write_keyword("ASOF RIGHT JOIN");
6353                    }
6354                }
6355                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
6356                JoinKind::LeftLateral => {
6357                    if join.use_outer_keyword {
6358                        self.write_keyword("LEFT OUTER LATERAL JOIN");
6359                    } else {
6360                        self.write_keyword("LEFT LATERAL JOIN");
6361                    }
6362                }
6363                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
6364                JoinKind::Implicit => {
6365                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
6366                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
6367                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
6368                    use crate::dialects::DialectType;
6369                    let is_cj_dialect = matches!(
6370                        self.config.dialect,
6371                        Some(DialectType::BigQuery)
6372                            | Some(DialectType::Hive)
6373                            | Some(DialectType::Spark)
6374                            | Some(DialectType::Databricks)
6375                    );
6376                    let source_is_same = self.config.source_dialect.is_some()
6377                        && self.config.source_dialect == self.config.dialect;
6378                    let source_is_cj = matches!(
6379                        self.config.source_dialect,
6380                        Some(DialectType::BigQuery)
6381                            | Some(DialectType::Hive)
6382                            | Some(DialectType::Spark)
6383                            | Some(DialectType::Databricks)
6384                    );
6385                    if is_cj_dialect
6386                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
6387                    {
6388                        self.write_keyword("CROSS JOIN");
6389                    } else {
6390                        // Implicit join uses comma: FROM a, b
6391                        // We already wrote a space before the match, so replace with comma
6392                        // by removing trailing space and writing ", "
6393                        self.output.truncate(self.output.trim_end().len());
6394                        self.write(",");
6395                    }
6396                }
6397                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
6398                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
6399                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
6400                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
6401            }
6402        }
6403
6404        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
6405        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
6406            match &join.this {
6407                Expression::Tuple(t) if t.expressions.is_empty() => {}
6408                Expression::Tuple(t) => {
6409                    self.write_space();
6410                    for (i, item) in t.expressions.iter().enumerate() {
6411                        if i > 0 {
6412                            self.write(", ");
6413                        }
6414                        self.generate_expression(item)?;
6415                    }
6416                }
6417                other => {
6418                    self.write_space();
6419                    self.generate_expression(other)?;
6420                }
6421            }
6422        } else {
6423            self.write_space();
6424            self.generate_expression(&join.this)?;
6425        }
6426
6427        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
6428        if !join.deferred_condition {
6429            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
6430            if let Some(match_cond) = &join.match_condition {
6431                self.write_space();
6432                self.write_keyword("MATCH_CONDITION");
6433                self.write(" (");
6434                self.generate_expression(match_cond)?;
6435                self.write(")");
6436            }
6437
6438            if let Some(on) = &join.on {
6439                if self.config.pretty {
6440                    self.write_newline();
6441                    self.indent_level += 1;
6442                    self.write_indent();
6443                    self.write_keyword("ON");
6444                    self.write_space();
6445                    self.generate_join_on_condition(on)?;
6446                    self.indent_level -= 1;
6447                } else {
6448                    self.write_space();
6449                    self.write_keyword("ON");
6450                    self.write_space();
6451                    self.generate_expression(on)?;
6452                }
6453            }
6454
6455            if !join.using.is_empty() {
6456                if self.config.pretty {
6457                    self.write_newline();
6458                    self.indent_level += 1;
6459                    self.write_indent();
6460                    self.write_keyword("USING");
6461                    self.write(" (");
6462                    for (i, col) in join.using.iter().enumerate() {
6463                        if i > 0 {
6464                            self.write(", ");
6465                        }
6466                        self.generate_identifier(col)?;
6467                    }
6468                    self.write(")");
6469                    self.indent_level -= 1;
6470                } else {
6471                    self.write_space();
6472                    self.write_keyword("USING");
6473                    self.write(" (");
6474                    for (i, col) in join.using.iter().enumerate() {
6475                        if i > 0 {
6476                            self.write(", ");
6477                        }
6478                        self.generate_identifier(col)?;
6479                    }
6480                    self.write(")");
6481                }
6482            }
6483        }
6484
6485        // Generate PIVOT/UNPIVOT expressions that follow this join
6486        for pivot in &join.pivots {
6487            self.write_space();
6488            self.generate_expression(pivot)?;
6489        }
6490
6491        Ok(())
6492    }
6493
6494    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
6495    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
6496        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
6497        if let Some(match_cond) = &join.match_condition {
6498            self.write_space();
6499            self.write_keyword("MATCH_CONDITION");
6500            self.write(" (");
6501            self.generate_expression(match_cond)?;
6502            self.write(")");
6503        }
6504
6505        if let Some(on) = &join.on {
6506            if self.config.pretty {
6507                self.write_newline();
6508                self.indent_level += 1;
6509                self.write_indent();
6510                self.write_keyword("ON");
6511                self.write_space();
6512                // In pretty mode, split AND conditions onto separate lines
6513                self.generate_join_on_condition(on)?;
6514                self.indent_level -= 1;
6515            } else {
6516                self.write_space();
6517                self.write_keyword("ON");
6518                self.write_space();
6519                self.generate_expression(on)?;
6520            }
6521        }
6522
6523        if !join.using.is_empty() {
6524            if self.config.pretty {
6525                self.write_newline();
6526                self.indent_level += 1;
6527                self.write_indent();
6528                self.write_keyword("USING");
6529                self.write(" (");
6530                for (i, col) in join.using.iter().enumerate() {
6531                    if i > 0 {
6532                        self.write(", ");
6533                    }
6534                    self.generate_identifier(col)?;
6535                }
6536                self.write(")");
6537                self.indent_level -= 1;
6538            } else {
6539                self.write_space();
6540                self.write_keyword("USING");
6541                self.write(" (");
6542                for (i, col) in join.using.iter().enumerate() {
6543                    if i > 0 {
6544                        self.write(", ");
6545                    }
6546                    self.generate_identifier(col)?;
6547                }
6548                self.write(")");
6549            }
6550        }
6551
6552        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
6553        for pivot in &join.pivots {
6554            self.write_space();
6555            self.generate_expression(pivot)?;
6556        }
6557
6558        Ok(())
6559    }
6560
6561    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
6562    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
6563        if let Expression::And(and_op) = expr {
6564            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
6565                self.generate_expression(conditions[0])?;
6566                for condition in conditions.iter().skip(1) {
6567                    self.write_newline();
6568                    self.write_indent();
6569                    self.write_keyword("AND");
6570                    self.write_space();
6571                    self.generate_expression(condition)?;
6572                }
6573                return Ok(());
6574            }
6575        }
6576
6577        self.generate_expression(expr)
6578    }
6579
6580    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
6581        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
6582        self.write("(");
6583        self.generate_expression(&jt.left)?;
6584
6585        // Generate all joins
6586        for join in &jt.joins {
6587            self.generate_join(join)?;
6588        }
6589
6590        // Generate LATERAL VIEW clauses (Hive/Spark)
6591        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
6592            self.generate_lateral_view(lv, lv_idx)?;
6593        }
6594
6595        self.write(")");
6596
6597        // Alias
6598        if let Some(alias) = &jt.alias {
6599            self.write_space();
6600            self.write_keyword("AS");
6601            self.write_space();
6602            self.generate_identifier(alias)?;
6603        }
6604
6605        Ok(())
6606    }
6607
6608    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
6609        use crate::dialects::DialectType;
6610
6611        if self.config.pretty {
6612            self.write_newline();
6613            self.write_indent();
6614        } else {
6615            self.write_space();
6616        }
6617
6618        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
6619        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
6620        let use_lateral_join = matches!(
6621            self.config.dialect,
6622            Some(DialectType::PostgreSQL)
6623                | Some(DialectType::DuckDB)
6624                | Some(DialectType::Snowflake)
6625                | Some(DialectType::TSQL)
6626                | Some(DialectType::Presto)
6627                | Some(DialectType::Trino)
6628                | Some(DialectType::Athena)
6629        );
6630
6631        // Check if target dialect should use UNNEST instead of EXPLODE
6632        let use_unnest = matches!(
6633            self.config.dialect,
6634            Some(DialectType::DuckDB)
6635                | Some(DialectType::Presto)
6636                | Some(DialectType::Trino)
6637                | Some(DialectType::Athena)
6638        );
6639
6640        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
6641        let (is_posexplode, is_inline, func_args) = match &lv.this {
6642            Expression::Explode(uf) => {
6643                // Expression::Explode is the dedicated EXPLODE expression type
6644                (false, false, vec![uf.this.clone()])
6645            }
6646            Expression::Unnest(uf) => {
6647                let mut args = vec![uf.this.clone()];
6648                args.extend(uf.expressions.clone());
6649                (false, false, args)
6650            }
6651            Expression::Function(func) => {
6652                if func.name.eq_ignore_ascii_case("POSEXPLODE")
6653                    || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER")
6654                {
6655                    (true, false, func.args.clone())
6656                } else if func.name.eq_ignore_ascii_case("INLINE") {
6657                    (false, true, func.args.clone())
6658                } else if func.name.eq_ignore_ascii_case("EXPLODE")
6659                    || func.name.eq_ignore_ascii_case("EXPLODE_OUTER")
6660                {
6661                    (false, false, func.args.clone())
6662                } else {
6663                    (false, false, vec![])
6664                }
6665            }
6666            _ => (false, false, vec![]),
6667        };
6668
6669        if use_lateral_join {
6670            // Convert to CROSS JOIN for PostgreSQL-like dialects
6671            if lv.outer {
6672                self.write_keyword("LEFT JOIN LATERAL");
6673            } else {
6674                self.write_keyword("CROSS JOIN");
6675            }
6676            self.write_space();
6677
6678            if use_unnest && !func_args.is_empty() {
6679                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
6680                // For DuckDB, also convert ARRAY(y) -> [y]
6681                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6682                    // DuckDB: ARRAY(y) -> [y]
6683                    func_args
6684                        .iter()
6685                        .map(|a| {
6686                            if let Expression::Function(ref f) = a {
6687                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
6688                                    return Expression::ArrayFunc(Box::new(
6689                                        crate::expressions::ArrayConstructor {
6690                                            expressions: f.args.clone(),
6691                                            bracket_notation: true,
6692                                            use_list_keyword: false,
6693                                        },
6694                                    ));
6695                                }
6696                            }
6697                            a.clone()
6698                        })
6699                        .collect::<Vec<_>>()
6700                } else if matches!(
6701                    self.config.dialect,
6702                    Some(DialectType::Presto)
6703                        | Some(DialectType::Trino)
6704                        | Some(DialectType::Athena)
6705                ) {
6706                    // Presto: ARRAY(y) -> ARRAY[y]
6707                    func_args
6708                        .iter()
6709                        .map(|a| {
6710                            if let Expression::Function(ref f) = a {
6711                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
6712                                    return Expression::ArrayFunc(Box::new(
6713                                        crate::expressions::ArrayConstructor {
6714                                            expressions: f.args.clone(),
6715                                            bracket_notation: true,
6716                                            use_list_keyword: false,
6717                                        },
6718                                    ));
6719                                }
6720                            }
6721                            a.clone()
6722                        })
6723                        .collect::<Vec<_>>()
6724                } else {
6725                    func_args
6726                };
6727
6728                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
6729                if is_posexplode {
6730                    self.write_keyword("LATERAL");
6731                    self.write(" (");
6732                    self.write_keyword("SELECT");
6733                    self.write_space();
6734
6735                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
6736                    // column_aliases[0] is the position column, rest are data columns
6737                    let pos_alias = if !lv.column_aliases.is_empty() {
6738                        lv.column_aliases[0].clone()
6739                    } else {
6740                        Identifier::new("pos")
6741                    };
6742                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
6743                        lv.column_aliases[1..].to_vec()
6744                    } else {
6745                        vec![Identifier::new("col")]
6746                    };
6747
6748                    // pos - 1 AS pos
6749                    self.generate_identifier(&pos_alias)?;
6750                    self.write(" - 1");
6751                    self.write_space();
6752                    self.write_keyword("AS");
6753                    self.write_space();
6754                    self.generate_identifier(&pos_alias)?;
6755
6756                    // , col [, key, value ...]
6757                    for data_col in &data_aliases {
6758                        self.write(", ");
6759                        self.generate_identifier(data_col)?;
6760                    }
6761
6762                    self.write_space();
6763                    self.write_keyword("FROM");
6764                    self.write_space();
6765                    self.write_keyword("UNNEST");
6766                    self.write("(");
6767                    for (i, arg) in unnest_args.iter().enumerate() {
6768                        if i > 0 {
6769                            self.write(", ");
6770                        }
6771                        self.generate_expression(arg)?;
6772                    }
6773                    self.write(")");
6774                    self.write_space();
6775                    self.write_keyword("WITH ORDINALITY");
6776                    self.write_space();
6777                    self.write_keyword("AS");
6778                    self.write_space();
6779
6780                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6781                    let table_alias_ident = lv
6782                        .table_alias
6783                        .clone()
6784                        .unwrap_or_else(|| Identifier::new("t"));
6785                    self.generate_identifier(&table_alias_ident)?;
6786                    self.write("(");
6787                    for (i, data_col) in data_aliases.iter().enumerate() {
6788                        if i > 0 {
6789                            self.write(", ");
6790                        }
6791                        self.generate_identifier(data_col)?;
6792                    }
6793                    self.write(", ");
6794                    self.generate_identifier(&pos_alias)?;
6795                    self.write("))");
6796                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6797                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6798                    self.write_keyword("LATERAL");
6799                    self.write(" (");
6800                    self.write_keyword("SELECT");
6801                    self.write_space();
6802                    self.write_keyword("UNNEST");
6803                    self.write("(");
6804                    for (i, arg) in unnest_args.iter().enumerate() {
6805                        if i > 0 {
6806                            self.write(", ");
6807                        }
6808                        self.generate_expression(arg)?;
6809                    }
6810                    self.write(", ");
6811                    self.write_keyword("max_depth");
6812                    self.write(" => 2))");
6813
6814                    // Add table and column aliases
6815                    if let Some(alias) = &lv.table_alias {
6816                        self.write_space();
6817                        self.write_keyword("AS");
6818                        self.write_space();
6819                        self.generate_identifier(alias)?;
6820                        if !lv.column_aliases.is_empty() {
6821                            self.write("(");
6822                            for (i, col) in lv.column_aliases.iter().enumerate() {
6823                                if i > 0 {
6824                                    self.write(", ");
6825                                }
6826                                self.generate_identifier(col)?;
6827                            }
6828                            self.write(")");
6829                        }
6830                    } else if !lv.column_aliases.is_empty() {
6831                        // Auto-generate alias like _u_N
6832                        self.write_space();
6833                        self.write_keyword("AS");
6834                        self.write_space();
6835                        self.write(&format!("_u_{}", lv_index));
6836                        self.write("(");
6837                        for (i, col) in lv.column_aliases.iter().enumerate() {
6838                            if i > 0 {
6839                                self.write(", ");
6840                            }
6841                            self.generate_identifier(col)?;
6842                        }
6843                        self.write(")");
6844                    }
6845                } else {
6846                    self.write_keyword("UNNEST");
6847                    self.write("(");
6848                    for (i, arg) in unnest_args.iter().enumerate() {
6849                        if i > 0 {
6850                            self.write(", ");
6851                        }
6852                        self.generate_expression(arg)?;
6853                    }
6854                    self.write(")");
6855
6856                    // Add table and column aliases for non-POSEXPLODE
6857                    if let Some(alias) = &lv.table_alias {
6858                        self.write_space();
6859                        self.write_keyword("AS");
6860                        self.write_space();
6861                        self.generate_identifier(alias)?;
6862                        if !lv.column_aliases.is_empty() {
6863                            self.write("(");
6864                            for (i, col) in lv.column_aliases.iter().enumerate() {
6865                                if i > 0 {
6866                                    self.write(", ");
6867                                }
6868                                self.generate_identifier(col)?;
6869                            }
6870                            self.write(")");
6871                        }
6872                    } else if !lv.column_aliases.is_empty() {
6873                        self.write_space();
6874                        self.write_keyword("AS");
6875                        self.write(" t(");
6876                        for (i, col) in lv.column_aliases.iter().enumerate() {
6877                            if i > 0 {
6878                                self.write(", ");
6879                            }
6880                            self.generate_identifier(col)?;
6881                        }
6882                        self.write(")");
6883                    }
6884                }
6885            } else {
6886                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6887                if !lv.outer {
6888                    self.write_keyword("LATERAL");
6889                    self.write_space();
6890                }
6891                self.generate_expression(&lv.this)?;
6892
6893                // Add table and column aliases
6894                if let Some(alias) = &lv.table_alias {
6895                    self.write_space();
6896                    self.write_keyword("AS");
6897                    self.write_space();
6898                    self.generate_identifier(alias)?;
6899                    if !lv.column_aliases.is_empty() {
6900                        self.write("(");
6901                        for (i, col) in lv.column_aliases.iter().enumerate() {
6902                            if i > 0 {
6903                                self.write(", ");
6904                            }
6905                            self.generate_identifier(col)?;
6906                        }
6907                        self.write(")");
6908                    }
6909                } else if !lv.column_aliases.is_empty() {
6910                    self.write_space();
6911                    self.write_keyword("AS");
6912                    self.write(" t(");
6913                    for (i, col) in lv.column_aliases.iter().enumerate() {
6914                        if i > 0 {
6915                            self.write(", ");
6916                        }
6917                        self.generate_identifier(col)?;
6918                    }
6919                    self.write(")");
6920                }
6921            }
6922
6923            // For LEFT JOIN LATERAL, need ON TRUE
6924            if lv.outer {
6925                self.write_space();
6926                self.write_keyword("ON TRUE");
6927            }
6928        } else {
6929            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6930            self.write_keyword("LATERAL VIEW");
6931            if lv.outer {
6932                self.write_space();
6933                self.write_keyword("OUTER");
6934            }
6935            if self.config.pretty {
6936                self.write_newline();
6937                self.write_indent();
6938            } else {
6939                self.write_space();
6940            }
6941            self.generate_expression(&lv.this)?;
6942
6943            // Table alias
6944            if let Some(alias) = &lv.table_alias {
6945                self.write_space();
6946                self.generate_identifier(alias)?;
6947            }
6948
6949            // Column aliases
6950            if !lv.column_aliases.is_empty() {
6951                self.write_space();
6952                self.write_keyword("AS");
6953                self.write_space();
6954                for (i, col) in lv.column_aliases.iter().enumerate() {
6955                    if i > 0 {
6956                        self.write(", ");
6957                    }
6958                    self.generate_identifier(col)?;
6959                }
6960            }
6961        }
6962
6963        Ok(())
6964    }
6965
6966    fn should_wrap_set_operation_modifiers(
6967        &self,
6968        order_by: &Option<OrderBy>,
6969        limit: &Option<Box<Expression>>,
6970        offset: &Option<Box<Expression>>,
6971    ) -> bool {
6972        let has_row_limit = limit.is_some() || offset.is_some();
6973        let has_emulated_null_ordering = order_by.as_ref().map_or(false, |order_by| {
6974            order_by
6975                .expressions
6976                .iter()
6977                .any(|ordered| ordered.nulls_first.is_some())
6978        });
6979
6980        (has_row_limit || has_emulated_null_ordering)
6981            && matches!(
6982                self.config.dialect,
6983                Some(DialectType::TSQL) | Some(DialectType::Fabric)
6984            )
6985    }
6986
6987    fn generate_tsql_wrapped_set_operation(
6988        &mut self,
6989        inner: Expression,
6990        with: Option<With>,
6991        order_by: Option<OrderBy>,
6992        limit: Option<Box<Expression>>,
6993        offset: Option<Box<Expression>>,
6994    ) -> Result<()> {
6995        let subquery = Subquery {
6996            this: inner,
6997            alias: Some(Identifier::new("_l_0".to_string())),
6998            column_aliases: Vec::new(),
6999            alias_explicit_as: true,
7000            alias_keyword: None,
7001            order_by: None,
7002            limit: None,
7003            offset: None,
7004            lateral: false,
7005            modifiers_inside: false,
7006            trailing_comments: Vec::new(),
7007            distribute_by: None,
7008            sort_by: None,
7009            cluster_by: None,
7010            inferred_type: None,
7011        };
7012
7013        let mut outer_select = Select {
7014            expressions: vec![Expression::Star(Star {
7015                table: None,
7016                except: None,
7017                replace: None,
7018                rename: None,
7019                trailing_comments: Vec::new(),
7020                span: None,
7021            })],
7022            from: Some(From {
7023                expressions: vec![Expression::Subquery(Box::new(subquery))],
7024            }),
7025            with,
7026            order_by,
7027            limit: limit.map(|limit| Limit {
7028                this: *limit,
7029                percent: false,
7030                comments: Vec::new(),
7031            }),
7032            offset: offset.map(|offset| Offset {
7033                this: *offset,
7034                rows: Some(true),
7035            }),
7036            ..Select::new()
7037        };
7038
7039        if outer_select.offset.is_some() && outer_select.order_by.is_none() {
7040            outer_select.order_by = Some(Self::dummy_tsql_order_by());
7041        }
7042
7043        self.generate_select(&outer_select)
7044    }
7045
7046    pub(crate) fn dummy_tsql_order_by() -> OrderBy {
7047        let null_select = Expression::Select(Box::new(Select {
7048            expressions: vec![Expression::Null(Null)],
7049            ..Select::new()
7050        }));
7051
7052        OrderBy {
7053            expressions: vec![Ordered {
7054                this: Expression::Subquery(Box::new(Subquery {
7055                    this: null_select,
7056                    alias: None,
7057                    column_aliases: Vec::new(),
7058                    alias_explicit_as: false,
7059                    alias_keyword: None,
7060                    order_by: None,
7061                    limit: None,
7062                    offset: None,
7063                    lateral: false,
7064                    modifiers_inside: false,
7065                    trailing_comments: Vec::new(),
7066                    distribute_by: None,
7067                    sort_by: None,
7068                    cluster_by: None,
7069                    inferred_type: None,
7070                })),
7071                desc: false,
7072                nulls_first: None,
7073                explicit_asc: false,
7074                with_fill: None,
7075            }],
7076            siblings: false,
7077            comments: Vec::new(),
7078        }
7079    }
7080
7081    fn generate_union(&mut self, outermost: &Union) -> Result<()> {
7082        if self.should_wrap_set_operation_modifiers(
7083            &outermost.order_by,
7084            &outermost.limit,
7085            &outermost.offset,
7086        ) {
7087            let mut inner = outermost.clone();
7088            let with = inner.with.take();
7089            let order_by = inner.order_by.take();
7090            let limit = inner.limit.take();
7091            let offset = inner.offset.take();
7092
7093            return self.generate_tsql_wrapped_set_operation(
7094                Expression::Union(Box::new(inner)),
7095                with,
7096                order_by,
7097                limit,
7098                offset,
7099            );
7100        }
7101
7102        // Collect the left-recursive chain of Union nodes iteratively.
7103        // This avoids stack overflow for deeply nested chains like
7104        // SELECT 1 UNION ALL SELECT 2 UNION ALL ... UNION ALL SELECT N
7105        // where the parser builds: Union(Union(Union(A, B), C), D)
7106        let mut chain: Vec<&Union> = vec![outermost];
7107        let mut leftmost: &Expression = &outermost.left;
7108        while let Expression::Union(inner) = leftmost {
7109            chain.push(inner);
7110            leftmost = &inner.left;
7111        }
7112        // chain[0] = outermost, chain[last] = innermost
7113        // leftmost = innermost.left (a non-Union expression, typically Select)
7114
7115        // WITH clause (only on outermost)
7116        if let Some(with) = &outermost.with {
7117            self.generate_with(with)?;
7118            self.write_space();
7119        }
7120
7121        // Generate the base (leftmost) expression
7122        self.generate_expression(leftmost)?;
7123
7124        // Generate each union step from innermost to outermost
7125        for union in chain.iter().rev() {
7126            self.generate_union_step(union)?;
7127        }
7128        Ok(())
7129    }
7130
7131    /// Generate a single UNION step: keyword, right expression, and trailing modifiers.
7132    fn generate_union_step(&mut self, union: &Union) -> Result<()> {
7133        if self.config.pretty {
7134            self.write_newline();
7135            self.write_indent();
7136        } else {
7137            self.write_space();
7138        }
7139
7140        // BigQuery set operation modifiers: [side] [kind] UNION
7141        if let Some(side) = &union.side {
7142            self.write_keyword(side);
7143            self.write_space();
7144        }
7145        if let Some(kind) = &union.kind {
7146            self.write_keyword(kind);
7147            self.write_space();
7148        }
7149
7150        self.write_keyword("UNION");
7151        if union.all {
7152            self.write_space();
7153            self.write_keyword("ALL");
7154        } else if union.distinct {
7155            self.write_space();
7156            self.write_keyword("DISTINCT");
7157        }
7158
7159        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7160        // DuckDB: BY NAME
7161        if union.corresponding || union.by_name {
7162            self.write_space();
7163            self.write_keyword("BY NAME");
7164        }
7165        if !union.on_columns.is_empty() {
7166            self.write_space();
7167            self.write_keyword("ON");
7168            self.write(" (");
7169            for (i, col) in union.on_columns.iter().enumerate() {
7170                if i > 0 {
7171                    self.write(", ");
7172                }
7173                self.generate_expression(col)?;
7174            }
7175            self.write(")");
7176        }
7177
7178        if self.config.pretty {
7179            self.write_newline();
7180            self.write_indent();
7181        } else {
7182            self.write_space();
7183        }
7184        self.generate_expression(&union.right)?;
7185        // ORDER BY, LIMIT, OFFSET for the set operation
7186        if let Some(order_by) = &union.order_by {
7187            if self.config.pretty {
7188                self.write_newline();
7189            } else {
7190                self.write_space();
7191            }
7192            self.write_keyword("ORDER BY");
7193            self.write_space();
7194            for (i, ordered) in order_by.expressions.iter().enumerate() {
7195                if i > 0 {
7196                    self.write(", ");
7197                }
7198                self.generate_ordered(ordered)?;
7199            }
7200        }
7201        if let Some(limit) = &union.limit {
7202            if self.config.pretty {
7203                self.write_newline();
7204            } else {
7205                self.write_space();
7206            }
7207            self.write_keyword("LIMIT");
7208            self.write_space();
7209            self.generate_expression(limit)?;
7210        }
7211        if let Some(offset) = &union.offset {
7212            if self.config.pretty {
7213                self.write_newline();
7214            } else {
7215                self.write_space();
7216            }
7217            self.write_keyword("OFFSET");
7218            self.write_space();
7219            self.generate_expression(offset)?;
7220        }
7221        // DISTRIBUTE BY (Hive/Spark)
7222        if let Some(distribute_by) = &union.distribute_by {
7223            self.write_space();
7224            self.write_keyword("DISTRIBUTE BY");
7225            self.write_space();
7226            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7227                if i > 0 {
7228                    self.write(", ");
7229                }
7230                self.generate_expression(expr)?;
7231            }
7232        }
7233        // SORT BY (Hive/Spark)
7234        if let Some(sort_by) = &union.sort_by {
7235            self.write_space();
7236            self.write_keyword("SORT BY");
7237            self.write_space();
7238            for (i, ord) in sort_by.expressions.iter().enumerate() {
7239                if i > 0 {
7240                    self.write(", ");
7241                }
7242                self.generate_ordered(ord)?;
7243            }
7244        }
7245        // CLUSTER BY (Hive/Spark)
7246        if let Some(cluster_by) = &union.cluster_by {
7247            self.write_space();
7248            self.write_keyword("CLUSTER BY");
7249            self.write_space();
7250            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7251                if i > 0 {
7252                    self.write(", ");
7253                }
7254                self.generate_ordered(ord)?;
7255            }
7256        }
7257        Ok(())
7258    }
7259
7260    fn generate_intersect(&mut self, outermost: &Intersect) -> Result<()> {
7261        if self.should_wrap_set_operation_modifiers(
7262            &outermost.order_by,
7263            &outermost.limit,
7264            &outermost.offset,
7265        ) {
7266            let mut inner = outermost.clone();
7267            let with = inner.with.take();
7268            let order_by = inner.order_by.take();
7269            let limit = inner.limit.take();
7270            let offset = inner.offset.take();
7271
7272            return self.generate_tsql_wrapped_set_operation(
7273                Expression::Intersect(Box::new(inner)),
7274                with,
7275                order_by,
7276                limit,
7277                offset,
7278            );
7279        }
7280
7281        // Collect the left-recursive chain iteratively to avoid stack overflow
7282        let mut chain: Vec<&Intersect> = vec![outermost];
7283        let mut leftmost: &Expression = &outermost.left;
7284        while let Expression::Intersect(inner) = leftmost {
7285            chain.push(inner);
7286            leftmost = &inner.left;
7287        }
7288
7289        if let Some(with) = &outermost.with {
7290            self.generate_with(with)?;
7291            self.write_space();
7292        }
7293
7294        self.generate_expression(leftmost)?;
7295
7296        for intersect in chain.iter().rev() {
7297            self.generate_intersect_step(intersect)?;
7298        }
7299        Ok(())
7300    }
7301
7302    /// Generate a single INTERSECT step: keyword, right expression, and trailing modifiers.
7303    fn generate_intersect_step(&mut self, intersect: &Intersect) -> Result<()> {
7304        if self.config.pretty {
7305            self.write_newline();
7306            self.write_indent();
7307        } else {
7308            self.write_space();
7309        }
7310
7311        // BigQuery set operation modifiers: [side] [kind] INTERSECT
7312        if let Some(side) = &intersect.side {
7313            self.write_keyword(side);
7314            self.write_space();
7315        }
7316        if let Some(kind) = &intersect.kind {
7317            self.write_keyword(kind);
7318            self.write_space();
7319        }
7320
7321        self.write_keyword("INTERSECT");
7322        if intersect.all {
7323            if !self.config.except_intersect_support_all_clause {
7324                self.unsupported("INTERSECT ALL is not supported")?;
7325            }
7326            self.write_space();
7327            self.write_keyword("ALL");
7328        } else if intersect.distinct {
7329            self.write_space();
7330            self.write_keyword("DISTINCT");
7331        }
7332
7333        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7334        // DuckDB: BY NAME
7335        if intersect.corresponding || intersect.by_name {
7336            self.write_space();
7337            self.write_keyword("BY NAME");
7338        }
7339        if !intersect.on_columns.is_empty() {
7340            self.write_space();
7341            self.write_keyword("ON");
7342            self.write(" (");
7343            for (i, col) in intersect.on_columns.iter().enumerate() {
7344                if i > 0 {
7345                    self.write(", ");
7346                }
7347                self.generate_expression(col)?;
7348            }
7349            self.write(")");
7350        }
7351
7352        if self.config.pretty {
7353            self.write_newline();
7354            self.write_indent();
7355        } else {
7356            self.write_space();
7357        }
7358        self.generate_expression(&intersect.right)?;
7359        // ORDER BY, LIMIT, OFFSET for the set operation
7360        if let Some(order_by) = &intersect.order_by {
7361            if self.config.pretty {
7362                self.write_newline();
7363            } else {
7364                self.write_space();
7365            }
7366            self.write_keyword("ORDER BY");
7367            self.write_space();
7368            for (i, ordered) in order_by.expressions.iter().enumerate() {
7369                if i > 0 {
7370                    self.write(", ");
7371                }
7372                self.generate_ordered(ordered)?;
7373            }
7374        }
7375        if let Some(limit) = &intersect.limit {
7376            if self.config.pretty {
7377                self.write_newline();
7378            } else {
7379                self.write_space();
7380            }
7381            self.write_keyword("LIMIT");
7382            self.write_space();
7383            self.generate_expression(limit)?;
7384        }
7385        if let Some(offset) = &intersect.offset {
7386            if self.config.pretty {
7387                self.write_newline();
7388            } else {
7389                self.write_space();
7390            }
7391            self.write_keyword("OFFSET");
7392            self.write_space();
7393            self.generate_expression(offset)?;
7394        }
7395        // DISTRIBUTE BY (Hive/Spark)
7396        if let Some(distribute_by) = &intersect.distribute_by {
7397            self.write_space();
7398            self.write_keyword("DISTRIBUTE BY");
7399            self.write_space();
7400            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7401                if i > 0 {
7402                    self.write(", ");
7403                }
7404                self.generate_expression(expr)?;
7405            }
7406        }
7407        // SORT BY (Hive/Spark)
7408        if let Some(sort_by) = &intersect.sort_by {
7409            self.write_space();
7410            self.write_keyword("SORT BY");
7411            self.write_space();
7412            for (i, ord) in sort_by.expressions.iter().enumerate() {
7413                if i > 0 {
7414                    self.write(", ");
7415                }
7416                self.generate_ordered(ord)?;
7417            }
7418        }
7419        // CLUSTER BY (Hive/Spark)
7420        if let Some(cluster_by) = &intersect.cluster_by {
7421            self.write_space();
7422            self.write_keyword("CLUSTER BY");
7423            self.write_space();
7424            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7425                if i > 0 {
7426                    self.write(", ");
7427                }
7428                self.generate_ordered(ord)?;
7429            }
7430        }
7431        Ok(())
7432    }
7433
7434    fn generate_except(&mut self, outermost: &Except) -> Result<()> {
7435        if self.should_wrap_set_operation_modifiers(
7436            &outermost.order_by,
7437            &outermost.limit,
7438            &outermost.offset,
7439        ) {
7440            let mut inner = outermost.clone();
7441            let with = inner.with.take();
7442            let order_by = inner.order_by.take();
7443            let limit = inner.limit.take();
7444            let offset = inner.offset.take();
7445
7446            return self.generate_tsql_wrapped_set_operation(
7447                Expression::Except(Box::new(inner)),
7448                with,
7449                order_by,
7450                limit,
7451                offset,
7452            );
7453        }
7454
7455        // Collect the left-recursive chain iteratively to avoid stack overflow
7456        let mut chain: Vec<&Except> = vec![outermost];
7457        let mut leftmost: &Expression = &outermost.left;
7458        while let Expression::Except(inner) = leftmost {
7459            chain.push(inner);
7460            leftmost = &inner.left;
7461        }
7462
7463        if let Some(with) = &outermost.with {
7464            self.generate_with(with)?;
7465            self.write_space();
7466        }
7467
7468        self.generate_expression(leftmost)?;
7469
7470        for except in chain.iter().rev() {
7471            self.generate_except_step(except)?;
7472        }
7473        Ok(())
7474    }
7475
7476    /// Generate a single EXCEPT step: keyword, right expression, and trailing modifiers.
7477    fn generate_except_step(&mut self, except: &Except) -> Result<()> {
7478        use crate::dialects::DialectType;
7479
7480        if self.config.pretty {
7481            self.write_newline();
7482            self.write_indent();
7483        } else {
7484            self.write_space();
7485        }
7486
7487        // BigQuery set operation modifiers: [side] [kind] EXCEPT
7488        if let Some(side) = &except.side {
7489            self.write_keyword(side);
7490            self.write_space();
7491        }
7492        if let Some(kind) = &except.kind {
7493            self.write_keyword(kind);
7494            self.write_space();
7495        }
7496
7497        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
7498        match self.config.dialect {
7499            Some(DialectType::Oracle) if !except.all => {
7500                self.write_keyword("MINUS");
7501            }
7502            Some(DialectType::ClickHouse) => {
7503                self.write_keyword("EXCEPT");
7504                let preserve_all = self.config.source_dialect.is_none()
7505                    || matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
7506                if except.all && preserve_all {
7507                    self.write_space();
7508                    self.write_keyword("ALL");
7509                }
7510                if except.distinct {
7511                    self.write_space();
7512                    self.write_keyword("DISTINCT");
7513                }
7514            }
7515            Some(DialectType::BigQuery) => {
7516                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
7517                self.write_keyword("EXCEPT");
7518                if except.all {
7519                    self.write_space();
7520                    self.write_keyword("ALL");
7521                } else {
7522                    self.write_space();
7523                    self.write_keyword("DISTINCT");
7524                }
7525            }
7526            _ => {
7527                self.write_keyword("EXCEPT");
7528                if except.all {
7529                    if !self.config.except_intersect_support_all_clause {
7530                        self.unsupported("EXCEPT ALL is not supported")?;
7531                    }
7532                    self.write_space();
7533                    self.write_keyword("ALL");
7534                } else if except.distinct {
7535                    self.write_space();
7536                    self.write_keyword("DISTINCT");
7537                }
7538            }
7539        }
7540
7541        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
7542        // DuckDB: BY NAME
7543        if except.corresponding || except.by_name {
7544            self.write_space();
7545            self.write_keyword("BY NAME");
7546        }
7547        if !except.on_columns.is_empty() {
7548            self.write_space();
7549            self.write_keyword("ON");
7550            self.write(" (");
7551            for (i, col) in except.on_columns.iter().enumerate() {
7552                if i > 0 {
7553                    self.write(", ");
7554                }
7555                self.generate_expression(col)?;
7556            }
7557            self.write(")");
7558        }
7559
7560        if self.config.pretty {
7561            self.write_newline();
7562            self.write_indent();
7563        } else {
7564            self.write_space();
7565        }
7566        self.generate_expression(&except.right)?;
7567        // ORDER BY, LIMIT, OFFSET for the set operation
7568        if let Some(order_by) = &except.order_by {
7569            if self.config.pretty {
7570                self.write_newline();
7571            } else {
7572                self.write_space();
7573            }
7574            self.write_keyword("ORDER BY");
7575            self.write_space();
7576            for (i, ordered) in order_by.expressions.iter().enumerate() {
7577                if i > 0 {
7578                    self.write(", ");
7579                }
7580                self.generate_ordered(ordered)?;
7581            }
7582        }
7583        if let Some(limit) = &except.limit {
7584            if self.config.pretty {
7585                self.write_newline();
7586            } else {
7587                self.write_space();
7588            }
7589            self.write_keyword("LIMIT");
7590            self.write_space();
7591            self.generate_expression(limit)?;
7592        }
7593        if let Some(offset) = &except.offset {
7594            if self.config.pretty {
7595                self.write_newline();
7596            } else {
7597                self.write_space();
7598            }
7599            self.write_keyword("OFFSET");
7600            self.write_space();
7601            self.generate_expression(offset)?;
7602        }
7603        // DISTRIBUTE BY (Hive/Spark)
7604        if let Some(distribute_by) = &except.distribute_by {
7605            self.write_space();
7606            self.write_keyword("DISTRIBUTE BY");
7607            self.write_space();
7608            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7609                if i > 0 {
7610                    self.write(", ");
7611                }
7612                self.generate_expression(expr)?;
7613            }
7614        }
7615        // SORT BY (Hive/Spark)
7616        if let Some(sort_by) = &except.sort_by {
7617            self.write_space();
7618            self.write_keyword("SORT BY");
7619            self.write_space();
7620            for (i, ord) in sort_by.expressions.iter().enumerate() {
7621                if i > 0 {
7622                    self.write(", ");
7623                }
7624                self.generate_ordered(ord)?;
7625            }
7626        }
7627        // CLUSTER BY (Hive/Spark)
7628        if let Some(cluster_by) = &except.cluster_by {
7629            self.write_space();
7630            self.write_keyword("CLUSTER BY");
7631            self.write_space();
7632            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7633                if i > 0 {
7634                    self.write(", ");
7635                }
7636                self.generate_ordered(ord)?;
7637            }
7638        }
7639        Ok(())
7640    }
7641
7642    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
7643        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
7644        let prepend_query_cte = if insert.with.is_none() {
7645            use crate::dialects::DialectType;
7646            let should_prepend = matches!(
7647                self.config.dialect,
7648                Some(DialectType::TSQL)
7649                    | Some(DialectType::Fabric)
7650                    | Some(DialectType::Spark)
7651                    | Some(DialectType::Databricks)
7652                    | Some(DialectType::Hive)
7653            );
7654            if should_prepend {
7655                if let Some(Expression::Select(select)) = &insert.query {
7656                    select.with.clone()
7657                } else {
7658                    None
7659                }
7660            } else {
7661                None
7662            }
7663        } else {
7664            None
7665        };
7666
7667        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
7668        if let Some(with) = &insert.with {
7669            self.generate_with(with)?;
7670            self.write_space();
7671        } else if let Some(with) = &prepend_query_cte {
7672            self.generate_with(with)?;
7673            self.write_space();
7674        }
7675
7676        // Output leading comments before INSERT
7677        for comment in &insert.leading_comments {
7678            self.write_formatted_comment(comment);
7679            self.write(" ");
7680        }
7681
7682        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
7683        if let Some(dir) = &insert.directory {
7684            self.write_keyword("INSERT OVERWRITE");
7685            if dir.local {
7686                self.write_space();
7687                self.write_keyword("LOCAL");
7688            }
7689            self.write_space();
7690            self.write_keyword("DIRECTORY");
7691            self.write_space();
7692            self.write("'");
7693            self.write(&dir.path);
7694            self.write("'");
7695
7696            // ROW FORMAT clause
7697            if let Some(row_format) = &dir.row_format {
7698                self.write_space();
7699                self.write_keyword("ROW FORMAT");
7700                if row_format.delimited {
7701                    self.write_space();
7702                    self.write_keyword("DELIMITED");
7703                }
7704                if let Some(val) = &row_format.fields_terminated_by {
7705                    self.write_space();
7706                    self.write_keyword("FIELDS TERMINATED BY");
7707                    self.write_space();
7708                    self.generate_string_literal(val)?;
7709                }
7710                if let Some(val) = &row_format.collection_items_terminated_by {
7711                    self.write_space();
7712                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
7713                    self.write_space();
7714                    self.write("'");
7715                    self.write(val);
7716                    self.write("'");
7717                }
7718                if let Some(val) = &row_format.map_keys_terminated_by {
7719                    self.write_space();
7720                    self.write_keyword("MAP KEYS TERMINATED BY");
7721                    self.write_space();
7722                    self.write("'");
7723                    self.write(val);
7724                    self.write("'");
7725                }
7726                if let Some(val) = &row_format.lines_terminated_by {
7727                    self.write_space();
7728                    self.write_keyword("LINES TERMINATED BY");
7729                    self.write_space();
7730                    self.write("'");
7731                    self.write(val);
7732                    self.write("'");
7733                }
7734                if let Some(val) = &row_format.null_defined_as {
7735                    self.write_space();
7736                    self.write_keyword("NULL DEFINED AS");
7737                    self.write_space();
7738                    self.write("'");
7739                    self.write(val);
7740                    self.write("'");
7741                }
7742            }
7743
7744            // STORED AS clause
7745            if let Some(format) = &dir.stored_as {
7746                self.write_space();
7747                self.write_keyword("STORED AS");
7748                self.write_space();
7749                self.write_keyword(format);
7750            }
7751
7752            // Query (SELECT statement)
7753            if let Some(query) = &insert.query {
7754                self.write_space();
7755                self.generate_expression(query)?;
7756            }
7757
7758            return Ok(());
7759        }
7760
7761        if insert.is_replace {
7762            // MySQL/SQLite REPLACE INTO statement
7763            self.write_keyword("REPLACE INTO");
7764        } else if insert.overwrite {
7765            // Use dialect-specific INSERT OVERWRITE format
7766            self.write_keyword("INSERT");
7767            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7768            if let Some(ref hint) = insert.hint {
7769                self.generate_hint(hint)?;
7770            }
7771            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
7772        } else if let Some(ref action) = insert.conflict_action {
7773            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
7774            self.write_keyword("INSERT OR");
7775            self.write_space();
7776            self.write_keyword(action);
7777            self.write_space();
7778            self.write_keyword("INTO");
7779        } else if insert.ignore {
7780            // MySQL INSERT IGNORE syntax
7781            self.write_keyword("INSERT IGNORE INTO");
7782        } else {
7783            self.write_keyword("INSERT");
7784            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7785            if let Some(ref hint) = insert.hint {
7786                self.generate_hint(hint)?;
7787            }
7788            self.write_space();
7789            self.write_keyword("INTO");
7790        }
7791        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
7792        if let Some(ref func) = insert.function_target {
7793            self.write_space();
7794            self.write_keyword("FUNCTION");
7795            self.write_space();
7796            self.generate_expression(func)?;
7797        } else {
7798            self.write_space();
7799            self.generate_table(&insert.table)?;
7800        }
7801
7802        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
7803        if let Some(ref alias) = insert.alias {
7804            self.write_space();
7805            if insert.alias_explicit_as {
7806                self.write_keyword("AS");
7807                self.write_space();
7808            }
7809            self.generate_identifier(alias)?;
7810        }
7811
7812        // IF EXISTS clause (Hive)
7813        if insert.if_exists {
7814            self.write_space();
7815            self.write_keyword("IF EXISTS");
7816        }
7817
7818        // REPLACE WHERE clause (Databricks)
7819        if let Some(ref replace_where) = insert.replace_where {
7820            if self.config.pretty {
7821                self.write_newline();
7822                self.write_indent();
7823            } else {
7824                self.write_space();
7825            }
7826            self.write_keyword("REPLACE WHERE");
7827            self.write_space();
7828            self.generate_expression(replace_where)?;
7829        }
7830
7831        // Generate PARTITION clause if present
7832        if !insert.partition.is_empty() {
7833            self.write_space();
7834            self.write_keyword("PARTITION");
7835            self.write("(");
7836            for (i, (col, val)) in insert.partition.iter().enumerate() {
7837                if i > 0 {
7838                    self.write(", ");
7839                }
7840                self.generate_identifier(col)?;
7841                if let Some(v) = val {
7842                    self.write(" = ");
7843                    self.generate_expression(v)?;
7844                }
7845            }
7846            self.write(")");
7847        }
7848
7849        // ClickHouse: PARTITION BY expr
7850        if let Some(ref partition_by) = insert.partition_by {
7851            self.write_space();
7852            self.write_keyword("PARTITION BY");
7853            self.write_space();
7854            self.generate_expression(partition_by)?;
7855        }
7856
7857        // ClickHouse: SETTINGS key = val, ...
7858        if !insert.settings.is_empty() {
7859            self.write_space();
7860            self.write_keyword("SETTINGS");
7861            self.write_space();
7862            for (i, setting) in insert.settings.iter().enumerate() {
7863                if i > 0 {
7864                    self.write(", ");
7865                }
7866                self.generate_expression(setting)?;
7867            }
7868        }
7869
7870        if !insert.columns.is_empty() {
7871            if insert.alias.is_some() && insert.alias_explicit_as {
7872                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
7873                self.write("(");
7874            } else {
7875                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
7876                self.write(" (");
7877            }
7878            for (i, col) in insert.columns.iter().enumerate() {
7879                if i > 0 {
7880                    self.write(", ");
7881                }
7882                self.generate_identifier(col)?;
7883            }
7884            self.write(")");
7885        }
7886
7887        // OUTPUT clause (TSQL)
7888        if let Some(ref output) = insert.output {
7889            self.generate_output_clause(output)?;
7890        }
7891
7892        // BY NAME modifier (DuckDB)
7893        if insert.by_name {
7894            self.write_space();
7895            self.write_keyword("BY NAME");
7896        }
7897
7898        if insert.default_values {
7899            self.write_space();
7900            self.write_keyword("DEFAULT VALUES");
7901        } else if let Some(query) = &insert.query {
7902            if self.config.pretty {
7903                self.write_newline();
7904            } else {
7905                self.write_space();
7906            }
7907            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
7908            if prepend_query_cte.is_some() {
7909                if let Expression::Select(select) = query {
7910                    let mut select_no_with = select.clone();
7911                    select_no_with.with = None;
7912                    self.generate_select(&select_no_with)?;
7913                } else {
7914                    self.generate_expression(query)?;
7915                }
7916            } else {
7917                self.generate_expression(query)?;
7918            }
7919        } else if !insert.values.is_empty() {
7920            if self.config.pretty {
7921                // Pretty printing: VALUES on new line, each tuple indented
7922                self.write_newline();
7923                self.write_keyword("VALUES");
7924                self.write_newline();
7925                self.indent_level += 1;
7926                for (i, row) in insert.values.iter().enumerate() {
7927                    if i > 0 {
7928                        self.write(",");
7929                        self.write_newline();
7930                    }
7931                    self.write_indent();
7932                    self.write("(");
7933                    for (j, val) in row.iter().enumerate() {
7934                        if j > 0 {
7935                            self.write(", ");
7936                        }
7937                        self.generate_expression(val)?;
7938                    }
7939                    self.write(")");
7940                }
7941                self.indent_level -= 1;
7942            } else {
7943                // Non-pretty: single line
7944                self.write_space();
7945                self.write_keyword("VALUES");
7946                for (i, row) in insert.values.iter().enumerate() {
7947                    if i > 0 {
7948                        self.write(",");
7949                    }
7950                    self.write(" (");
7951                    for (j, val) in row.iter().enumerate() {
7952                        if j > 0 {
7953                            self.write(", ");
7954                        }
7955                        self.generate_expression(val)?;
7956                    }
7957                    self.write(")");
7958                }
7959            }
7960        }
7961
7962        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
7963        if let Some(ref source) = insert.source {
7964            self.write_space();
7965            self.write_keyword("TABLE");
7966            self.write_space();
7967            self.generate_expression(source)?;
7968        }
7969
7970        // Source alias (MySQL: VALUES (...) AS new_data)
7971        if let Some(alias) = &insert.source_alias {
7972            self.write_space();
7973            self.write_keyword("AS");
7974            self.write_space();
7975            self.generate_identifier(alias)?;
7976        }
7977
7978        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
7979        if let Some(on_conflict) = &insert.on_conflict {
7980            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
7981                self.write_space();
7982                self.generate_expression(on_conflict)?;
7983            }
7984        }
7985
7986        // RETURNING clause
7987        if !insert.returning.is_empty() {
7988            self.write_space();
7989            self.write_keyword("RETURNING");
7990            self.write_space();
7991            for (i, expr) in insert.returning.iter().enumerate() {
7992                if i > 0 {
7993                    self.write(", ");
7994                }
7995                self.generate_expression(expr)?;
7996            }
7997        }
7998
7999        Ok(())
8000    }
8001
8002    fn generate_update(&mut self, update: &Update) -> Result<()> {
8003        // Output leading comments before UPDATE
8004        for comment in &update.leading_comments {
8005            self.write_formatted_comment(comment);
8006            self.write(" ");
8007        }
8008
8009        // WITH clause (CTEs)
8010        if let Some(ref with) = update.with {
8011            self.generate_with(with)?;
8012            self.write_space();
8013        }
8014
8015        self.write_keyword("UPDATE");
8016        if let Some(hint) = &update.hint {
8017            self.generate_hint(hint)?;
8018        }
8019        self.write_space();
8020        self.generate_table(&update.table)?;
8021
8022        let mysql_like_update_from = matches!(
8023            self.config.dialect,
8024            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
8025        ) && update.from_clause.is_some();
8026
8027        let mut set_pairs = update.set.clone();
8028
8029        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
8030        let mut pre_set_joins = update.table_joins.clone();
8031        if mysql_like_update_from {
8032            let target_name = update
8033                .table
8034                .alias
8035                .as_ref()
8036                .map(|a| a.name.clone())
8037                .unwrap_or_else(|| update.table.name.name.clone());
8038
8039            for (col, _) in &mut set_pairs {
8040                if !col.name.contains('.') {
8041                    col.name = format!("{}.{}", target_name, col.name);
8042                }
8043            }
8044
8045            if let Some(from_clause) = &update.from_clause {
8046                for table_expr in &from_clause.expressions {
8047                    pre_set_joins.push(crate::expressions::Join {
8048                        this: table_expr.clone(),
8049                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
8050                            value: true,
8051                        })),
8052                        using: Vec::new(),
8053                        kind: crate::expressions::JoinKind::Inner,
8054                        use_inner_keyword: false,
8055                        use_outer_keyword: false,
8056                        deferred_condition: false,
8057                        join_hint: None,
8058                        match_condition: None,
8059                        pivots: Vec::new(),
8060                        comments: Vec::new(),
8061                        nesting_group: 0,
8062                        directed: false,
8063                    });
8064                }
8065            }
8066            for join in &update.from_joins {
8067                let mut join = join.clone();
8068                if join.on.is_none() && join.using.is_empty() {
8069                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
8070                        value: true,
8071                    }));
8072                }
8073                pre_set_joins.push(join);
8074            }
8075        }
8076
8077        // Extra tables for multi-table UPDATE (MySQL syntax)
8078        for extra_table in &update.extra_tables {
8079            self.write(", ");
8080            self.generate_table(extra_table)?;
8081        }
8082
8083        // JOINs attached to the table list (MySQL multi-table syntax)
8084        for join in &pre_set_joins {
8085            // generate_join already adds a leading space
8086            self.generate_join(join)?;
8087        }
8088
8089        // Teradata: FROM clause comes before SET
8090        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
8091        if teradata_from_before_set && !mysql_like_update_from {
8092            if let Some(ref from_clause) = update.from_clause {
8093                self.write_space();
8094                self.write_keyword("FROM");
8095                self.write_space();
8096                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
8097                    if i > 0 {
8098                        self.write(", ");
8099                    }
8100                    self.generate_expression(table_expr)?;
8101                }
8102            }
8103            for join in &update.from_joins {
8104                self.generate_join(join)?;
8105            }
8106        }
8107
8108        self.write_space();
8109        self.write_keyword("SET");
8110        self.write_space();
8111
8112        for (i, (col, val)) in set_pairs.iter().enumerate() {
8113            if i > 0 {
8114                self.write(", ");
8115            }
8116            self.generate_identifier(col)?;
8117            self.write(" = ");
8118            self.generate_expression(val)?;
8119        }
8120
8121        // OUTPUT clause (TSQL)
8122        if let Some(ref output) = update.output {
8123            self.generate_output_clause(output)?;
8124        }
8125
8126        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
8127        if !mysql_like_update_from && !teradata_from_before_set {
8128            if let Some(ref from_clause) = update.from_clause {
8129                self.write_space();
8130                self.write_keyword("FROM");
8131                self.write_space();
8132                // Generate each table in the FROM clause
8133                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
8134                    if i > 0 {
8135                        self.write(", ");
8136                    }
8137                    self.generate_expression(table_expr)?;
8138                }
8139            }
8140        }
8141
8142        if !mysql_like_update_from && !teradata_from_before_set {
8143            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
8144            for join in &update.from_joins {
8145                self.generate_join(join)?;
8146            }
8147        }
8148
8149        if let Some(where_clause) = &update.where_clause {
8150            self.write_space();
8151            self.write_keyword("WHERE");
8152            self.write_space();
8153            self.generate_expression(&where_clause.this)?;
8154        }
8155
8156        // RETURNING clause
8157        if !update.returning.is_empty() {
8158            self.write_space();
8159            self.write_keyword("RETURNING");
8160            self.write_space();
8161            for (i, expr) in update.returning.iter().enumerate() {
8162                if i > 0 {
8163                    self.write(", ");
8164                }
8165                self.generate_expression(expr)?;
8166            }
8167        }
8168
8169        // ORDER BY clause (MySQL)
8170        if let Some(ref order_by) = update.order_by {
8171            self.write_space();
8172            self.generate_order_by(order_by)?;
8173        }
8174
8175        // LIMIT clause (MySQL)
8176        if let Some(ref limit) = update.limit {
8177            self.write_space();
8178            self.write_keyword("LIMIT");
8179            self.write_space();
8180            self.generate_expression(limit)?;
8181        }
8182
8183        Ok(())
8184    }
8185
8186    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
8187        // Output WITH clause if present
8188        if let Some(with) = &delete.with {
8189            self.generate_with(with)?;
8190            self.write_space();
8191        }
8192
8193        // Output leading comments before DELETE
8194        for comment in &delete.leading_comments {
8195            self.write_formatted_comment(comment);
8196            self.write(" ");
8197        }
8198
8199        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
8200        if !delete.tables.is_empty() && !delete.tables_from_using {
8201            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
8202            self.write_keyword("DELETE");
8203            if let Some(hint) = &delete.hint {
8204                self.generate_hint(hint)?;
8205            }
8206            self.write_space();
8207            for (i, tbl) in delete.tables.iter().enumerate() {
8208                if i > 0 {
8209                    self.write(", ");
8210                }
8211                self.generate_table(tbl)?;
8212            }
8213            // TSQL: OUTPUT clause between target table and FROM
8214            if let Some(ref output) = delete.output {
8215                self.generate_output_clause(output)?;
8216            }
8217            self.write_space();
8218            self.write_keyword("FROM");
8219            self.write_space();
8220            self.generate_table(&delete.table)?;
8221        } else if !delete.tables.is_empty() && delete.tables_from_using {
8222            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
8223            self.write_keyword("DELETE");
8224            if let Some(hint) = &delete.hint {
8225                self.generate_hint(hint)?;
8226            }
8227            self.write_space();
8228            self.write_keyword("FROM");
8229            self.write_space();
8230            for (i, tbl) in delete.tables.iter().enumerate() {
8231                if i > 0 {
8232                    self.write(", ");
8233                }
8234                self.generate_table(tbl)?;
8235            }
8236        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
8237            // BigQuery-style DELETE without FROM keyword
8238            self.write_keyword("DELETE");
8239            if let Some(hint) = &delete.hint {
8240                self.generate_hint(hint)?;
8241            }
8242            self.write_space();
8243            self.generate_table(&delete.table)?;
8244        } else {
8245            self.write_keyword("DELETE");
8246            if let Some(hint) = &delete.hint {
8247                self.generate_hint(hint)?;
8248            }
8249            self.write_space();
8250            self.write_keyword("FROM");
8251            self.write_space();
8252            self.generate_table(&delete.table)?;
8253        }
8254
8255        // ClickHouse: ON CLUSTER clause
8256        if let Some(ref on_cluster) = delete.on_cluster {
8257            self.write_space();
8258            self.generate_on_cluster(on_cluster)?;
8259        }
8260
8261        // FORCE INDEX hint (MySQL)
8262        if let Some(ref idx) = delete.force_index {
8263            self.write_space();
8264            self.write_keyword("FORCE INDEX");
8265            self.write(" (");
8266            self.write(idx);
8267            self.write(")");
8268        }
8269
8270        // Optional alias
8271        if let Some(ref alias) = delete.alias {
8272            self.write_space();
8273            if delete.alias_explicit_as
8274                || matches!(self.config.dialect, Some(DialectType::BigQuery))
8275            {
8276                self.write_keyword("AS");
8277                self.write_space();
8278            }
8279            self.generate_identifier(alias)?;
8280        }
8281
8282        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
8283        if !delete.tables_from_using {
8284            for join in &delete.joins {
8285                self.generate_join(join)?;
8286            }
8287        }
8288
8289        // USING clause (PostgreSQL/DuckDB/MySQL)
8290        if !delete.using.is_empty() {
8291            self.write_space();
8292            self.write_keyword("USING");
8293            for (i, table) in delete.using.iter().enumerate() {
8294                if i > 0 {
8295                    self.write(",");
8296                }
8297                self.write_space();
8298                // Check if the table has subquery hints (DuckDB USING with subquery)
8299                if !table.hints.is_empty() && table.name.is_empty() {
8300                    // Subquery in USING: (VALUES ...) AS alias(cols)
8301                    self.generate_expression(&table.hints[0])?;
8302                    if let Some(ref alias) = table.alias {
8303                        self.write_space();
8304                        if table.alias_explicit_as {
8305                            self.write_keyword("AS");
8306                            self.write_space();
8307                        }
8308                        self.generate_identifier(alias)?;
8309                        if !table.column_aliases.is_empty() {
8310                            self.write("(");
8311                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
8312                                if j > 0 {
8313                                    self.write(", ");
8314                                }
8315                                self.generate_identifier(col_alias)?;
8316                            }
8317                            self.write(")");
8318                        }
8319                    }
8320                } else {
8321                    self.generate_table(table)?;
8322                }
8323            }
8324        }
8325
8326        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
8327        if delete.tables_from_using {
8328            for join in &delete.joins {
8329                self.generate_join(join)?;
8330            }
8331        }
8332
8333        // OUTPUT clause (TSQL) - only if not already emitted in the early position
8334        let output_already_emitted =
8335            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
8336        if !output_already_emitted {
8337            if let Some(ref output) = delete.output {
8338                self.generate_output_clause(output)?;
8339            }
8340        }
8341
8342        if let Some(where_clause) = &delete.where_clause {
8343            self.write_space();
8344            self.write_keyword("WHERE");
8345            self.write_space();
8346            self.generate_expression(&where_clause.this)?;
8347        }
8348
8349        // ORDER BY clause (MySQL)
8350        if let Some(ref order_by) = delete.order_by {
8351            self.write_space();
8352            self.generate_order_by(order_by)?;
8353        }
8354
8355        // LIMIT clause (MySQL)
8356        if let Some(ref limit) = delete.limit {
8357            self.write_space();
8358            self.write_keyword("LIMIT");
8359            self.write_space();
8360            self.generate_expression(limit)?;
8361        }
8362
8363        // RETURNING clause (PostgreSQL)
8364        if !delete.returning.is_empty() {
8365            self.write_space();
8366            self.write_keyword("RETURNING");
8367            self.write_space();
8368            for (i, expr) in delete.returning.iter().enumerate() {
8369                if i > 0 {
8370                    self.write(", ");
8371                }
8372                self.generate_expression(expr)?;
8373            }
8374        }
8375
8376        Ok(())
8377    }
8378
8379    // ==================== DDL Generation ====================
8380
8381    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
8382        // Athena: Determine if this is Hive-style DDL or Trino-style DML
8383        // CREATE TABLE AS SELECT uses Trino (double quotes)
8384        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
8385        let saved_athena_hive_context = self.athena_hive_context;
8386        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
8387        if matches!(
8388            self.config.dialect,
8389            Some(crate::dialects::DialectType::Athena)
8390        ) {
8391            // Use Hive context if:
8392            // 1. It's an EXTERNAL table, OR
8393            // 2. There's no AS SELECT clause
8394            let is_external = ct
8395                .table_modifier
8396                .as_ref()
8397                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
8398                .unwrap_or(false);
8399            let has_as_select = ct.as_select.is_some();
8400            self.athena_hive_context = is_external || !has_as_select;
8401        }
8402
8403        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
8404        if matches!(
8405            self.config.dialect,
8406            Some(crate::dialects::DialectType::TSQL)
8407        ) {
8408            if let Some(ref query) = ct.as_select {
8409                // Output WITH CTE clause if present
8410                if let Some(with_cte) = &ct.with_cte {
8411                    self.generate_with(with_cte)?;
8412                    self.write_space();
8413                }
8414
8415                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
8416                self.write_keyword("SELECT");
8417                self.write(" * ");
8418                self.write_keyword("INTO");
8419                self.write_space();
8420
8421                // If temporary, prefix with # for TSQL temp table
8422                if ct.temporary {
8423                    self.write("#");
8424                }
8425                self.generate_table(&ct.name)?;
8426
8427                self.write_space();
8428                self.write_keyword("FROM");
8429                self.write(" (");
8430                // For TSQL, add aliases to select columns to preserve column names
8431                let aliased_query = Self::add_column_aliases_to_query(query.clone());
8432                self.generate_expression(&aliased_query)?;
8433                self.write(") ");
8434                self.write_keyword("AS");
8435                self.write(" temp");
8436                return Ok(());
8437            }
8438        }
8439
8440        // Output WITH CTE clause if present
8441        if let Some(with_cte) = &ct.with_cte {
8442            self.generate_with(with_cte)?;
8443            self.write_space();
8444        }
8445
8446        // Output leading comments before CREATE
8447        for comment in &ct.leading_comments {
8448            self.write_formatted_comment(comment);
8449            self.write(" ");
8450        }
8451        self.write_keyword("CREATE");
8452
8453        if ct.or_replace {
8454            self.write_space();
8455            self.write_keyword("OR REPLACE");
8456        }
8457
8458        if ct.temporary {
8459            self.write_space();
8460            // Oracle uses GLOBAL TEMPORARY TABLE syntax
8461            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
8462                self.write_keyword("GLOBAL TEMPORARY");
8463            } else {
8464                self.write_keyword("TEMPORARY");
8465            }
8466        }
8467
8468        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
8469        let is_dictionary = ct
8470            .table_modifier
8471            .as_ref()
8472            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
8473            .unwrap_or(false);
8474        if let Some(ref modifier) = ct.table_modifier {
8475            // TRANSIENT is Snowflake-specific - skip for other dialects
8476            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
8477                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
8478            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
8479            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
8480                || modifier.eq_ignore_ascii_case("SET")
8481                || modifier.eq_ignore_ascii_case("MULTISET")
8482                || modifier.to_ascii_uppercase().contains("VOLATILE")
8483                || modifier.to_ascii_uppercase().starts_with("SET ")
8484                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
8485            let skip_teradata =
8486                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
8487            if !skip_transient && !skip_teradata {
8488                self.write_space();
8489                self.write_keyword(modifier);
8490            }
8491        }
8492
8493        if !is_dictionary {
8494            self.write_space();
8495            self.write_keyword("TABLE");
8496        }
8497
8498        if ct.if_not_exists {
8499            self.write_space();
8500            self.write_keyword("IF NOT EXISTS");
8501        }
8502
8503        self.write_space();
8504        self.generate_table(&ct.name)?;
8505
8506        // ClickHouse: UUID 'xxx' clause after table name
8507        if let Some(ref uuid) = ct.uuid {
8508            self.write_space();
8509            self.write_keyword("UUID");
8510            self.write(" '");
8511            self.write(uuid);
8512            self.write("'");
8513        }
8514
8515        // ClickHouse: ON CLUSTER clause
8516        if let Some(ref on_cluster) = ct.on_cluster {
8517            self.write_space();
8518            self.generate_on_cluster(on_cluster)?;
8519        }
8520
8521        // Teradata: options after table name before column list (comma-separated)
8522        if matches!(
8523            self.config.dialect,
8524            Some(crate::dialects::DialectType::Teradata)
8525        ) && !ct.teradata_post_name_options.is_empty()
8526        {
8527            for opt in &ct.teradata_post_name_options {
8528                self.write(", ");
8529                self.write(opt);
8530            }
8531        }
8532
8533        // Snowflake: COPY GRANTS clause
8534        if ct.copy_grants {
8535            self.write_space();
8536            self.write_keyword("COPY GRANTS");
8537        }
8538
8539        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
8540        if let Some(ref using_template) = ct.using_template {
8541            self.write_space();
8542            self.write_keyword("USING TEMPLATE");
8543            self.write_space();
8544            self.generate_expression(using_template)?;
8545            return Ok(());
8546        }
8547
8548        // ClickHouse uses CREATE TABLE target AS source [ENGINE ...] for table-structure copies.
8549        // When explicit columns or constraints are present, the source must be emitted
8550        // after the parenthesized schema: CREATE TABLE target (cols) AS source.
8551        if is_clickhouse {
8552            if let Some(ref clone_source) = ct.clone_source {
8553                if ct.columns.is_empty() && ct.constraints.is_empty() {
8554                    self.write_space();
8555                    self.write_keyword("AS");
8556                    self.write_space();
8557                    self.generate_table(clone_source)?;
8558                }
8559            }
8560        }
8561
8562        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
8563        if !is_clickhouse {
8564            if let Some(ref clone_source) = ct.clone_source {
8565                self.write_space();
8566                if ct.is_copy && self.config.supports_table_copy {
8567                    // BigQuery uses COPY
8568                    self.write_keyword("COPY");
8569                } else if ct.shallow_clone {
8570                    self.write_keyword("SHALLOW CLONE");
8571                } else if ct.deep_clone {
8572                    self.write_keyword("DEEP CLONE");
8573                } else {
8574                    self.write_keyword("CLONE");
8575                }
8576                self.write_space();
8577                self.generate_table(clone_source)?;
8578                // Generate AT/BEFORE time travel clause (stored as Raw expression)
8579                if let Some(ref at_clause) = ct.clone_at_clause {
8580                    self.write_space();
8581                    self.generate_expression(at_clause)?;
8582                }
8583                return Ok(());
8584            }
8585        }
8586
8587        // Handle PARTITION OF property
8588        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
8589        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
8590        if let Some(ref partition_of) = ct.partition_of {
8591            self.write_space();
8592
8593            // Extract the PartitionedOfProperty parts to generate them separately
8594            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
8595                // Output: PARTITION OF <table>
8596                self.write_keyword("PARTITION OF");
8597                self.write_space();
8598                self.generate_expression(&pop.this)?;
8599
8600                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
8601                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
8602                    self.write(" (");
8603                    let mut first = true;
8604                    for col in &ct.columns {
8605                        if !first {
8606                            self.write(", ");
8607                        }
8608                        first = false;
8609                        self.generate_column_def(col)?;
8610                    }
8611                    for constraint in &ct.constraints {
8612                        if !first {
8613                            self.write(", ");
8614                        }
8615                        first = false;
8616                        self.generate_table_constraint(constraint)?;
8617                    }
8618                    self.write(")");
8619                }
8620
8621                // Output partition bound spec: FOR VALUES ... or DEFAULT
8622                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
8623                    self.write_space();
8624                    self.write_keyword("FOR VALUES");
8625                    self.write_space();
8626                    self.generate_expression(&pop.expression)?;
8627                } else {
8628                    self.write_space();
8629                    self.write_keyword("DEFAULT");
8630                }
8631            } else {
8632                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
8633                self.generate_expression(partition_of)?;
8634
8635                // Output columns/constraints if present
8636                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
8637                    self.write(" (");
8638                    let mut first = true;
8639                    for col in &ct.columns {
8640                        if !first {
8641                            self.write(", ");
8642                        }
8643                        first = false;
8644                        self.generate_column_def(col)?;
8645                    }
8646                    for constraint in &ct.constraints {
8647                        if !first {
8648                            self.write(", ");
8649                        }
8650                        first = false;
8651                        self.generate_table_constraint(constraint)?;
8652                    }
8653                    self.write(")");
8654                }
8655            }
8656
8657            // Output table properties (e.g., PARTITION BY RANGE(population))
8658            for prop in &ct.properties {
8659                self.write_space();
8660                self.generate_expression(prop)?;
8661            }
8662
8663            return Ok(());
8664        }
8665
8666        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
8667        // This matches Python sqlglot's behavior for SQLite dialect
8668        self.sqlite_inline_pk_columns.clear();
8669        if matches!(
8670            self.config.dialect,
8671            Some(crate::dialects::DialectType::SQLite)
8672        ) {
8673            for constraint in &ct.constraints {
8674                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8675                    // Only inline if: single column, no constraint name, and column exists in table
8676                    if columns.len() == 1 && name.is_none() {
8677                        let pk_col_name = columns[0].name.to_ascii_lowercase();
8678                        // Check if this column exists in the table
8679                        if ct
8680                            .columns
8681                            .iter()
8682                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
8683                        {
8684                            self.sqlite_inline_pk_columns.insert(pk_col_name);
8685                        }
8686                    }
8687                }
8688            }
8689        }
8690
8691        // Output columns if present (even for CTAS with columns)
8692        if !ct.columns.is_empty() {
8693            if self.config.pretty {
8694                // Pretty print: each column on new line
8695                self.write(" (");
8696                self.write_newline();
8697                self.indent_level += 1;
8698                for (i, col) in ct.columns.iter().enumerate() {
8699                    if i > 0 {
8700                        self.write(",");
8701                        self.write_newline();
8702                    }
8703                    self.write_indent();
8704                    self.generate_column_def(col)?;
8705                }
8706                // Table constraints (skip inlined PRIMARY KEY for SQLite)
8707                for constraint in &ct.constraints {
8708                    // Skip single-column PRIMARY KEY that was inlined for SQLite
8709                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8710                        if columns.len() == 1
8711                            && name.is_none()
8712                            && self
8713                                .sqlite_inline_pk_columns
8714                                .contains(&columns[0].name.to_ascii_lowercase())
8715                        {
8716                            continue;
8717                        }
8718                    }
8719                    self.write(",");
8720                    self.write_newline();
8721                    self.write_indent();
8722                    self.generate_table_constraint(constraint)?;
8723                }
8724                self.indent_level -= 1;
8725                self.write_newline();
8726                self.write(")");
8727            } else {
8728                self.write(" (");
8729                for (i, col) in ct.columns.iter().enumerate() {
8730                    if i > 0 {
8731                        self.write(", ");
8732                    }
8733                    self.generate_column_def(col)?;
8734                }
8735                // Table constraints (skip inlined PRIMARY KEY for SQLite)
8736                let mut first_constraint = true;
8737                for constraint in &ct.constraints {
8738                    // Skip single-column PRIMARY KEY that was inlined for SQLite
8739                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8740                        if columns.len() == 1
8741                            && name.is_none()
8742                            && self
8743                                .sqlite_inline_pk_columns
8744                                .contains(&columns[0].name.to_ascii_lowercase())
8745                        {
8746                            continue;
8747                        }
8748                    }
8749                    if first_constraint {
8750                        self.write(", ");
8751                        first_constraint = false;
8752                    } else {
8753                        self.write(", ");
8754                    }
8755                    self.generate_table_constraint(constraint)?;
8756                }
8757                self.write(")");
8758            }
8759        } else if !ct.constraints.is_empty() {
8760            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
8761            let has_like_only = ct
8762                .constraints
8763                .iter()
8764                .all(|c| matches!(c, TableConstraint::Like { .. }));
8765            let has_tags_only = ct
8766                .constraints
8767                .iter()
8768                .all(|c| matches!(c, TableConstraint::Tags(_)));
8769            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
8770            // Most dialects: CREATE TABLE A LIKE B (no parens)
8771            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
8772            let is_pg_like = matches!(
8773                self.config.dialect,
8774                Some(crate::dialects::DialectType::PostgreSQL)
8775                    | Some(crate::dialects::DialectType::CockroachDB)
8776                    | Some(crate::dialects::DialectType::Materialize)
8777                    | Some(crate::dialects::DialectType::RisingWave)
8778                    | Some(crate::dialects::DialectType::Redshift)
8779                    | Some(crate::dialects::DialectType::Presto)
8780                    | Some(crate::dialects::DialectType::Trino)
8781                    | Some(crate::dialects::DialectType::Athena)
8782            );
8783            let use_parens = if has_like_only {
8784                is_pg_like
8785            } else {
8786                !has_tags_only
8787            };
8788            if self.config.pretty && use_parens {
8789                self.write(" (");
8790                self.write_newline();
8791                self.indent_level += 1;
8792                for (i, constraint) in ct.constraints.iter().enumerate() {
8793                    if i > 0 {
8794                        self.write(",");
8795                        self.write_newline();
8796                    }
8797                    self.write_indent();
8798                    self.generate_table_constraint(constraint)?;
8799                }
8800                self.indent_level -= 1;
8801                self.write_newline();
8802                self.write(")");
8803            } else {
8804                if use_parens {
8805                    self.write(" (");
8806                } else {
8807                    self.write_space();
8808                }
8809                for (i, constraint) in ct.constraints.iter().enumerate() {
8810                    if i > 0 {
8811                        self.write(", ");
8812                    }
8813                    self.generate_table_constraint(constraint)?;
8814                }
8815                if use_parens {
8816                    self.write(")");
8817                }
8818            }
8819        }
8820
8821        if is_clickhouse && (!ct.columns.is_empty() || !ct.constraints.is_empty()) {
8822            if let Some(ref clone_source) = ct.clone_source {
8823                self.write_space();
8824                self.write_keyword("AS");
8825                self.write_space();
8826                self.generate_table(clone_source)?;
8827            }
8828        }
8829
8830        // TSQL ON filegroup or ON filegroup (partition_column) clause
8831        if let Some(ref on_prop) = ct.on_property {
8832            self.write(" ");
8833            self.write_keyword("ON");
8834            self.write(" ");
8835            self.generate_expression(&on_prop.this)?;
8836        }
8837
8838        // BigQuery: WITH PARTITION COLUMNS (col_name col_type, ...)
8839        if !ct.with_partition_columns.is_empty() {
8840            if self.config.pretty {
8841                self.write_newline();
8842            } else {
8843                self.write_space();
8844            }
8845            self.write_keyword("WITH PARTITION COLUMNS");
8846            self.write(" (");
8847            if self.config.pretty {
8848                self.write_newline();
8849                self.indent_level += 1;
8850                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8851                    if i > 0 {
8852                        self.write(",");
8853                        self.write_newline();
8854                    }
8855                    self.write_indent();
8856                    self.generate_column_def(col)?;
8857                }
8858                self.indent_level -= 1;
8859                self.write_newline();
8860            } else {
8861                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8862                    if i > 0 {
8863                        self.write(", ");
8864                    }
8865                    self.generate_column_def(col)?;
8866                }
8867            }
8868            self.write(")");
8869        }
8870
8871        // BigQuery: WITH CONNECTION `project.region.connection`
8872        if let Some(ref conn) = ct.with_connection {
8873            if self.config.pretty {
8874                self.write_newline();
8875            } else {
8876                self.write_space();
8877            }
8878            self.write_keyword("WITH CONNECTION");
8879            self.write_space();
8880            self.generate_table(conn)?;
8881        }
8882
8883        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
8884        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
8885        if !is_clickhouse {
8886            for prop in &ct.properties {
8887                if let Expression::SchemaCommentProperty(_) = prop {
8888                    if self.config.pretty {
8889                        self.write_newline();
8890                    } else {
8891                        self.write_space();
8892                    }
8893                    self.generate_expression(prop)?;
8894                }
8895            }
8896        }
8897
8898        // WITH properties (output after columns if columns exist, otherwise before AS)
8899        if !ct.with_properties.is_empty() {
8900            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
8901            let is_snowflake_special_table = matches!(
8902                self.config.dialect,
8903                Some(crate::dialects::DialectType::Snowflake)
8904            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
8905                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
8906            if is_snowflake_special_table {
8907                for (key, value) in &ct.with_properties {
8908                    self.write_space();
8909                    self.write(key);
8910                    self.write("=");
8911                    self.write(value);
8912                }
8913            } else if self.config.pretty {
8914                self.write_newline();
8915                self.write_keyword("WITH");
8916                self.write(" (");
8917                self.write_newline();
8918                self.indent_level += 1;
8919                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8920                    if i > 0 {
8921                        self.write(",");
8922                        self.write_newline();
8923                    }
8924                    self.write_indent();
8925                    self.write(key);
8926                    self.write("=");
8927                    self.write(value);
8928                }
8929                self.indent_level -= 1;
8930                self.write_newline();
8931                self.write(")");
8932            } else {
8933                self.write_space();
8934                self.write_keyword("WITH");
8935                self.write(" (");
8936                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8937                    if i > 0 {
8938                        self.write(", ");
8939                    }
8940                    self.write(key);
8941                    self.write("=");
8942                    self.write(value);
8943                }
8944                self.write(")");
8945            }
8946        }
8947
8948        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
8949            if is_clickhouse && ct.as_select.is_some() {
8950                let mut pre = Vec::new();
8951                let mut post = Vec::new();
8952                for prop in &ct.properties {
8953                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
8954                        post.push(prop);
8955                    } else {
8956                        pre.push(prop);
8957                    }
8958                }
8959                (pre, post)
8960            } else {
8961                (ct.properties.iter().collect(), Vec::new())
8962            };
8963
8964        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
8965        for prop in pre_as_properties {
8966            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
8967            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
8968                continue;
8969            }
8970            if self.config.pretty {
8971                self.write_newline();
8972            } else {
8973                self.write_space();
8974            }
8975            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
8976            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
8977            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
8978            if let Expression::Properties(props) = prop {
8979                let is_hive_dialect = matches!(
8980                    self.config.dialect,
8981                    Some(crate::dialects::DialectType::Hive)
8982                        | Some(crate::dialects::DialectType::Spark)
8983                        | Some(crate::dialects::DialectType::Databricks)
8984                        | Some(crate::dialects::DialectType::Athena)
8985                );
8986                let is_doris_starrocks = matches!(
8987                    self.config.dialect,
8988                    Some(crate::dialects::DialectType::Doris)
8989                        | Some(crate::dialects::DialectType::StarRocks)
8990                );
8991                if is_hive_dialect {
8992                    self.generate_tblproperties_clause(&props.expressions)?;
8993                } else if is_doris_starrocks {
8994                    self.generate_properties_clause(&props.expressions)?;
8995                } else {
8996                    self.generate_options_clause(&props.expressions)?;
8997                }
8998            } else {
8999                self.generate_expression(prop)?;
9000            }
9001        }
9002
9003        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
9004        for prop in &ct.post_table_properties {
9005            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
9006                self.write(" WITH(");
9007                self.generate_system_versioning_content(svp)?;
9008                self.write(")");
9009            } else if let Expression::Properties(props) = prop {
9010                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
9011                let is_doris_starrocks = matches!(
9012                    self.config.dialect,
9013                    Some(crate::dialects::DialectType::Doris)
9014                        | Some(crate::dialects::DialectType::StarRocks)
9015                );
9016                self.write_space();
9017                if is_doris_starrocks {
9018                    self.generate_properties_clause(&props.expressions)?;
9019                } else {
9020                    self.generate_options_clause(&props.expressions)?;
9021                }
9022            } else {
9023                self.write_space();
9024                self.generate_expression(prop)?;
9025            }
9026        }
9027
9028        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
9029        // Only output for StarRocks target
9030        if let Some(ref rollup) = ct.rollup {
9031            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
9032                self.write_space();
9033                self.generate_rollup_property(rollup)?;
9034            }
9035        }
9036
9037        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
9038        // Only output for MySQL-compatible dialects; strip for others during transpilation
9039        // COMMENT is also used by Hive/Spark so we selectively preserve it
9040        let is_mysql_compatible = matches!(
9041            self.config.dialect,
9042            Some(DialectType::MySQL)
9043                | Some(DialectType::SingleStore)
9044                | Some(DialectType::Doris)
9045                | Some(DialectType::StarRocks)
9046                | None
9047        );
9048        let is_hive_compatible = matches!(
9049            self.config.dialect,
9050            Some(DialectType::Hive)
9051                | Some(DialectType::Spark)
9052                | Some(DialectType::Databricks)
9053                | Some(DialectType::Athena)
9054        );
9055        let mysql_pretty_options =
9056            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
9057        for (key, value) in &ct.mysql_table_options {
9058            // Skip non-MySQL-specific options for non-MySQL targets
9059            let should_output = if is_mysql_compatible {
9060                true
9061            } else if is_hive_compatible && key == "COMMENT" {
9062                true // COMMENT is valid in Hive/Spark table definitions
9063            } else {
9064                false
9065            };
9066            if should_output {
9067                if mysql_pretty_options {
9068                    self.write_newline();
9069                    self.write_indent();
9070                } else {
9071                    self.write_space();
9072                }
9073                self.write_keyword(key);
9074                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
9075                if key == "COMMENT" && !self.config.schema_comment_with_eq {
9076                    self.write_space();
9077                } else {
9078                    self.write("=");
9079                }
9080                self.write(value);
9081            }
9082        }
9083
9084        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
9085        if ct.temporary
9086            && matches!(
9087                self.config.dialect,
9088                Some(DialectType::Spark) | Some(DialectType::Databricks)
9089            )
9090            && ct.as_select.is_none()
9091        {
9092            self.write_space();
9093            self.write_keyword("USING PARQUET");
9094        }
9095
9096        // PostgreSQL INHERITS clause
9097        if !ct.inherits.is_empty() {
9098            self.write_space();
9099            self.write_keyword("INHERITS");
9100            self.write(" (");
9101            for (i, parent) in ct.inherits.iter().enumerate() {
9102                if i > 0 {
9103                    self.write(", ");
9104                }
9105                self.generate_table(parent)?;
9106            }
9107            self.write(")");
9108        }
9109
9110        // CREATE TABLE AS SELECT
9111        if let Some(ref query) = ct.as_select {
9112            self.write_space();
9113            self.write_keyword("AS");
9114            self.write_space();
9115            let source_is_clickhouse =
9116                matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
9117            let wrap_as_select =
9118                ct.as_select_parenthesized && !(is_clickhouse && source_is_clickhouse);
9119            if wrap_as_select {
9120                self.write("(");
9121            }
9122            self.generate_expression(query)?;
9123            if wrap_as_select {
9124                self.write(")");
9125            }
9126
9127            // Teradata: WITH DATA / WITH NO DATA
9128            if let Some(with_data) = ct.with_data {
9129                self.write_space();
9130                self.write_keyword("WITH");
9131                if !with_data {
9132                    self.write_space();
9133                    self.write_keyword("NO");
9134                }
9135                self.write_space();
9136                self.write_keyword("DATA");
9137            }
9138
9139            // Teradata: AND STATISTICS / AND NO STATISTICS
9140            if let Some(with_statistics) = ct.with_statistics {
9141                self.write_space();
9142                self.write_keyword("AND");
9143                if !with_statistics {
9144                    self.write_space();
9145                    self.write_keyword("NO");
9146                }
9147                self.write_space();
9148                self.write_keyword("STATISTICS");
9149            }
9150
9151            // Teradata: Index specifications
9152            for index in &ct.teradata_indexes {
9153                self.write_space();
9154                match index.kind {
9155                    TeradataIndexKind::NoPrimary => {
9156                        self.write_keyword("NO PRIMARY INDEX");
9157                    }
9158                    TeradataIndexKind::Primary => {
9159                        self.write_keyword("PRIMARY INDEX");
9160                    }
9161                    TeradataIndexKind::PrimaryAmp => {
9162                        self.write_keyword("PRIMARY AMP INDEX");
9163                    }
9164                    TeradataIndexKind::Unique => {
9165                        self.write_keyword("UNIQUE INDEX");
9166                    }
9167                    TeradataIndexKind::UniquePrimary => {
9168                        self.write_keyword("UNIQUE PRIMARY INDEX");
9169                    }
9170                    TeradataIndexKind::Secondary => {
9171                        self.write_keyword("INDEX");
9172                    }
9173                }
9174                // Output index name if present
9175                if let Some(ref name) = index.name {
9176                    self.write_space();
9177                    self.write(name);
9178                }
9179                // Output columns if present
9180                if !index.columns.is_empty() {
9181                    self.write(" (");
9182                    for (i, col) in index.columns.iter().enumerate() {
9183                        if i > 0 {
9184                            self.write(", ");
9185                        }
9186                        self.write(col);
9187                    }
9188                    self.write(")");
9189                }
9190            }
9191
9192            // Teradata: ON COMMIT behavior for volatile tables
9193            if let Some(ref on_commit) = ct.on_commit {
9194                self.write_space();
9195                self.write_keyword("ON COMMIT");
9196                self.write_space();
9197                match on_commit {
9198                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
9199                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
9200                }
9201            }
9202
9203            if !post_as_properties.is_empty() {
9204                for prop in post_as_properties {
9205                    self.write_space();
9206                    self.generate_expression(prop)?;
9207                }
9208            }
9209
9210            // Restore Athena Hive context before early return
9211            self.athena_hive_context = saved_athena_hive_context;
9212            return Ok(());
9213        }
9214
9215        // ON COMMIT behavior (for non-CTAS tables)
9216        if let Some(ref on_commit) = ct.on_commit {
9217            self.write_space();
9218            self.write_keyword("ON COMMIT");
9219            self.write_space();
9220            match on_commit {
9221                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
9222                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
9223            }
9224        }
9225
9226        // Restore Athena Hive context
9227        self.athena_hive_context = saved_athena_hive_context;
9228
9229        Ok(())
9230    }
9231
9232    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
9233    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
9234    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
9235        // Output column name
9236        self.generate_identifier(&col.name)?;
9237        // Output data type if known
9238        if !matches!(col.data_type, DataType::Unknown) {
9239            self.write_space();
9240            self.generate_data_type(&col.data_type)?;
9241        }
9242        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
9243        for constraint in &col.constraints {
9244            if let ColumnConstraint::Path(path_expr) = constraint {
9245                self.write_space();
9246                self.write_keyword("PATH");
9247                self.write_space();
9248                self.generate_expression(path_expr)?;
9249            }
9250        }
9251        Ok(())
9252    }
9253
9254    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
9255        // Check if this is a TSQL computed column (no data type)
9256        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
9257            && col
9258                .constraints
9259                .iter()
9260                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
9261        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
9262        let omit_computed_type = !self.config.computed_column_with_type
9263            && col
9264                .constraints
9265                .iter()
9266                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
9267
9268        // Check if this is a partition column spec (no data type, type is Unknown)
9269        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
9270        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
9271
9272        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
9273        // Also check the no_type flag for SQLite columns without types
9274        let has_no_type = col.no_type
9275            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
9276                && col.constraints.is_empty());
9277
9278        self.generate_identifier(&col.name)?;
9279
9280        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
9281        let serial_expansion = if matches!(
9282            self.config.dialect,
9283            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
9284        ) {
9285            if let DataType::Custom { ref name } = col.data_type {
9286                if name.eq_ignore_ascii_case("SERIAL") {
9287                    Some("INT")
9288                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
9289                    Some("BIGINT")
9290                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
9291                    Some("SMALLINT")
9292                } else {
9293                    None
9294                }
9295            } else {
9296                None
9297            }
9298        } else {
9299            None
9300        };
9301
9302        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
9303        {
9304            self.write_space();
9305            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
9306            // since ClickHouse uses explicit Nullable() in its type system.
9307            let saved_nullable_depth = self.clickhouse_nullable_depth;
9308            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
9309                self.clickhouse_nullable_depth = -1;
9310            }
9311            if let Some(int_type) = serial_expansion {
9312                // SERIAL -> INT (+ constraints added below)
9313                self.write_keyword(int_type);
9314            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
9315                // For DuckDB: convert unsigned integer types to their unsigned equivalents
9316                let unsigned_type = match &col.data_type {
9317                    DataType::Int { .. } => Some("UINTEGER"),
9318                    DataType::BigInt { .. } => Some("UBIGINT"),
9319                    DataType::SmallInt { .. } => Some("USMALLINT"),
9320                    DataType::TinyInt { .. } => Some("UTINYINT"),
9321                    _ => None,
9322                };
9323                if let Some(utype) = unsigned_type {
9324                    self.write_keyword(utype);
9325                } else {
9326                    self.generate_data_type(&col.data_type)?;
9327                }
9328            } else {
9329                self.generate_data_type(&col.data_type)?;
9330            }
9331            self.clickhouse_nullable_depth = saved_nullable_depth;
9332        }
9333
9334        // MySQL type modifiers (must come right after data type)
9335        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
9336        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
9337            self.write_space();
9338            self.write_keyword("UNSIGNED");
9339        }
9340        if col.zerofill {
9341            self.write_space();
9342            self.write_keyword("ZEROFILL");
9343        }
9344
9345        // Teradata column attributes (must come right after data type, in specific order)
9346        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
9347
9348        if let Some(ref charset) = col.character_set {
9349            self.write_space();
9350            self.write_keyword("CHARACTER SET");
9351            self.write_space();
9352            self.write(charset);
9353        }
9354
9355        if col.uppercase {
9356            self.write_space();
9357            self.write_keyword("UPPERCASE");
9358        }
9359
9360        if let Some(casespecific) = col.casespecific {
9361            self.write_space();
9362            if casespecific {
9363                self.write_keyword("CASESPECIFIC");
9364            } else {
9365                self.write_keyword("NOT CASESPECIFIC");
9366            }
9367        }
9368
9369        if let Some(ref format) = col.format {
9370            self.write_space();
9371            self.write_keyword("FORMAT");
9372            self.write(" '");
9373            self.write(format);
9374            self.write("'");
9375        }
9376
9377        if let Some(ref title) = col.title {
9378            self.write_space();
9379            self.write_keyword("TITLE");
9380            self.write(" '");
9381            self.write(title);
9382            self.write("'");
9383        }
9384
9385        if let Some(length) = col.inline_length {
9386            self.write_space();
9387            self.write_keyword("INLINE LENGTH");
9388            self.write(" ");
9389            self.write(&length.to_string());
9390        }
9391
9392        if let Some(ref compress) = col.compress {
9393            self.write_space();
9394            self.write_keyword("COMPRESS");
9395            if !compress.is_empty() {
9396                // Single string literal: output without parentheses (Teradata syntax)
9397                if compress.len() == 1 {
9398                    if let Expression::Literal(lit) = &compress[0] {
9399                        if let Literal::String(_) = lit.as_ref() {
9400                            self.write_space();
9401                            self.generate_expression(&compress[0])?;
9402                        }
9403                    } else {
9404                        self.write(" (");
9405                        self.generate_expression(&compress[0])?;
9406                        self.write(")");
9407                    }
9408                } else {
9409                    self.write(" (");
9410                    for (i, val) in compress.iter().enumerate() {
9411                        if i > 0 {
9412                            self.write(", ");
9413                        }
9414                        self.generate_expression(val)?;
9415                    }
9416                    self.write(")");
9417                }
9418            }
9419        }
9420
9421        // Column constraints - output in original order if constraint_order is populated
9422        // Otherwise fall back to legacy fixed order for backward compatibility
9423        if !col.constraint_order.is_empty() {
9424            // Use constraint_order for original ordering
9425            // Track indices for constraints stored in the constraints Vec
9426            let mut references_idx = 0;
9427            let mut check_idx = 0;
9428            let mut generated_idx = 0;
9429            let mut collate_idx = 0;
9430            let mut comment_idx = 0;
9431            // The preprocessing in dialects/mod.rs now handles the correct ordering of
9432            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
9433            let defer_not_null_after_identity = false;
9434            let mut pending_not_null_after_identity = false;
9435
9436            for constraint_type in &col.constraint_order {
9437                match constraint_type {
9438                    ConstraintType::PrimaryKey => {
9439                        // Materialize doesn't support PRIMARY KEY column constraints
9440                        if col.primary_key
9441                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
9442                        {
9443                            if let Some(ref cname) = col.primary_key_constraint_name {
9444                                self.write_space();
9445                                self.write_keyword("CONSTRAINT");
9446                                self.write_space();
9447                                self.write(cname);
9448                            }
9449                            self.write_space();
9450                            self.write_keyword("PRIMARY KEY");
9451                            if let Some(ref order) = col.primary_key_order {
9452                                self.write_space();
9453                                match order {
9454                                    SortOrder::Asc => self.write_keyword("ASC"),
9455                                    SortOrder::Desc => self.write_keyword("DESC"),
9456                                }
9457                            }
9458                        }
9459                    }
9460                    ConstraintType::Unique => {
9461                        if col.unique {
9462                            if let Some(ref cname) = col.unique_constraint_name {
9463                                self.write_space();
9464                                self.write_keyword("CONSTRAINT");
9465                                self.write_space();
9466                                self.write(cname);
9467                            }
9468                            self.write_space();
9469                            self.write_keyword("UNIQUE");
9470                            // PostgreSQL 15+: NULLS NOT DISTINCT
9471                            if col.unique_nulls_not_distinct {
9472                                self.write(" NULLS NOT DISTINCT");
9473                            }
9474                        }
9475                    }
9476                    ConstraintType::NotNull => {
9477                        if col.nullable == Some(false) {
9478                            if defer_not_null_after_identity {
9479                                pending_not_null_after_identity = true;
9480                                continue;
9481                            }
9482                            if let Some(ref cname) = col.not_null_constraint_name {
9483                                self.write_space();
9484                                self.write_keyword("CONSTRAINT");
9485                                self.write_space();
9486                                self.write(cname);
9487                            }
9488                            self.write_space();
9489                            self.write_keyword("NOT NULL");
9490                        }
9491                    }
9492                    ConstraintType::Null => {
9493                        if col.nullable == Some(true) {
9494                            self.write_space();
9495                            self.write_keyword("NULL");
9496                        }
9497                    }
9498                    ConstraintType::Default => {
9499                        if let Some(ref default) = col.default {
9500                            self.write_space();
9501                            self.write_keyword("DEFAULT");
9502                            self.write_space();
9503                            self.generate_expression(default)?;
9504                        }
9505                    }
9506                    ConstraintType::AutoIncrement => {
9507                        if col.auto_increment {
9508                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
9509                            if matches!(
9510                                self.config.dialect,
9511                                Some(crate::dialects::DialectType::DuckDB)
9512                            ) {
9513                                // Skip - DuckDB uses sequences or rowid instead
9514                            } else if matches!(
9515                                self.config.dialect,
9516                                Some(crate::dialects::DialectType::Materialize)
9517                            ) {
9518                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
9519                                if !matches!(col.nullable, Some(false)) {
9520                                    self.write_space();
9521                                    self.write_keyword("NOT NULL");
9522                                }
9523                            } else if matches!(
9524                                self.config.dialect,
9525                                Some(crate::dialects::DialectType::PostgreSQL)
9526                            ) {
9527                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
9528                                self.write_space();
9529                                self.generate_auto_increment_keyword(col)?;
9530                            } else if matches!(
9531                                self.config.dialect,
9532                                Some(crate::dialects::DialectType::SQLite)
9533                            ) && !col.primary_key
9534                                && self
9535                                    .sqlite_inline_pk_columns
9536                                    .contains(&col.name.name.to_ascii_lowercase())
9537                            {
9538                                // SQLite requires AUTOINCREMENT after PRIMARY KEY.
9539                                // The table-level primary key is emitted later inline.
9540                            } else {
9541                                self.write_space();
9542                                self.generate_auto_increment_keyword(col)?;
9543                                if pending_not_null_after_identity {
9544                                    self.write_space();
9545                                    self.write_keyword("NOT NULL");
9546                                    pending_not_null_after_identity = false;
9547                                }
9548                            }
9549                        } // close else for DuckDB skip
9550                    }
9551                    ConstraintType::References => {
9552                        // Find next References constraint
9553                        while references_idx < col.constraints.len() {
9554                            if let ColumnConstraint::References(fk_ref) =
9555                                &col.constraints[references_idx]
9556                            {
9557                                // CONSTRAINT name if present
9558                                if let Some(ref name) = fk_ref.constraint_name {
9559                                    self.write_space();
9560                                    self.write_keyword("CONSTRAINT");
9561                                    self.write_space();
9562                                    self.write(name);
9563                                }
9564                                self.write_space();
9565                                if fk_ref.has_foreign_key_keywords {
9566                                    self.write_keyword("FOREIGN KEY");
9567                                    self.write_space();
9568                                }
9569                                self.write_keyword("REFERENCES");
9570                                self.write_space();
9571                                self.generate_table(&fk_ref.table)?;
9572                                if !fk_ref.columns.is_empty() {
9573                                    self.write(" (");
9574                                    for (i, c) in fk_ref.columns.iter().enumerate() {
9575                                        if i > 0 {
9576                                            self.write(", ");
9577                                        }
9578                                        self.generate_identifier(c)?;
9579                                    }
9580                                    self.write(")");
9581                                }
9582                                self.generate_referential_actions(fk_ref)?;
9583                                references_idx += 1;
9584                                break;
9585                            }
9586                            references_idx += 1;
9587                        }
9588                    }
9589                    ConstraintType::Check => {
9590                        // Find next Check constraint
9591                        while check_idx < col.constraints.len() {
9592                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
9593                                // Output CONSTRAINT name if present (only for first CHECK)
9594                                if check_idx == 0 {
9595                                    if let Some(ref cname) = col.check_constraint_name {
9596                                        self.write_space();
9597                                        self.write_keyword("CONSTRAINT");
9598                                        self.write_space();
9599                                        self.write(cname);
9600                                    }
9601                                }
9602                                self.write_space();
9603                                self.write_keyword("CHECK");
9604                                self.write(" (");
9605                                self.generate_expression(expr)?;
9606                                self.write(")");
9607                                check_idx += 1;
9608                                break;
9609                            }
9610                            check_idx += 1;
9611                        }
9612                    }
9613                    ConstraintType::GeneratedAsIdentity => {
9614                        // Find next GeneratedAsIdentity constraint
9615                        while generated_idx < col.constraints.len() {
9616                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
9617                                &col.constraints[generated_idx]
9618                            {
9619                                self.write_space();
9620                                // Redshift uses IDENTITY(start, increment) syntax
9621                                if matches!(
9622                                    self.config.dialect,
9623                                    Some(crate::dialects::DialectType::Redshift)
9624                                ) {
9625                                    self.write_keyword("IDENTITY");
9626                                    self.write("(");
9627                                    if let Some(ref start) = gen.start {
9628                                        self.generate_expression(start)?;
9629                                    } else {
9630                                        self.write("0");
9631                                    }
9632                                    self.write(", ");
9633                                    if let Some(ref incr) = gen.increment {
9634                                        self.generate_expression(incr)?;
9635                                    } else {
9636                                        self.write("1");
9637                                    }
9638                                    self.write(")");
9639                                } else {
9640                                    self.write_keyword("GENERATED");
9641                                    if gen.always {
9642                                        self.write_space();
9643                                        self.write_keyword("ALWAYS");
9644                                    } else {
9645                                        self.write_space();
9646                                        self.write_keyword("BY DEFAULT");
9647                                        if gen.on_null {
9648                                            self.write_space();
9649                                            self.write_keyword("ON NULL");
9650                                        }
9651                                    }
9652                                    self.write_space();
9653                                    self.write_keyword("AS IDENTITY");
9654
9655                                    let has_options = gen.start.is_some()
9656                                        || gen.increment.is_some()
9657                                        || gen.minvalue.is_some()
9658                                        || gen.maxvalue.is_some()
9659                                        || gen.cycle.is_some();
9660                                    if has_options {
9661                                        self.write(" (");
9662                                        let mut first = true;
9663                                        if let Some(ref start) = gen.start {
9664                                            if !first {
9665                                                self.write(" ");
9666                                            }
9667                                            first = false;
9668                                            self.write_keyword("START WITH");
9669                                            self.write_space();
9670                                            self.generate_expression(start)?;
9671                                        }
9672                                        if let Some(ref incr) = gen.increment {
9673                                            if !first {
9674                                                self.write(" ");
9675                                            }
9676                                            first = false;
9677                                            self.write_keyword("INCREMENT BY");
9678                                            self.write_space();
9679                                            self.generate_expression(incr)?;
9680                                        }
9681                                        if let Some(ref minv) = gen.minvalue {
9682                                            if !first {
9683                                                self.write(" ");
9684                                            }
9685                                            first = false;
9686                                            self.write_keyword("MINVALUE");
9687                                            self.write_space();
9688                                            self.generate_expression(minv)?;
9689                                        }
9690                                        if let Some(ref maxv) = gen.maxvalue {
9691                                            if !first {
9692                                                self.write(" ");
9693                                            }
9694                                            first = false;
9695                                            self.write_keyword("MAXVALUE");
9696                                            self.write_space();
9697                                            self.generate_expression(maxv)?;
9698                                        }
9699                                        if let Some(cycle) = gen.cycle {
9700                                            if !first {
9701                                                self.write(" ");
9702                                            }
9703                                            if cycle {
9704                                                self.write_keyword("CYCLE");
9705                                            } else {
9706                                                self.write_keyword("NO CYCLE");
9707                                            }
9708                                        }
9709                                        self.write(")");
9710                                    }
9711                                }
9712                                generated_idx += 1;
9713                                break;
9714                            }
9715                            generated_idx += 1;
9716                        }
9717                    }
9718                    ConstraintType::Collate => {
9719                        // Find next Collate constraint
9720                        while collate_idx < col.constraints.len() {
9721                            if let ColumnConstraint::Collate(collation) =
9722                                &col.constraints[collate_idx]
9723                            {
9724                                self.write_space();
9725                                self.write_keyword("COLLATE");
9726                                self.write_space();
9727                                self.generate_identifier(collation)?;
9728                                collate_idx += 1;
9729                                break;
9730                            }
9731                            collate_idx += 1;
9732                        }
9733                    }
9734                    ConstraintType::Comment => {
9735                        // Find next Comment constraint
9736                        while comment_idx < col.constraints.len() {
9737                            if let ColumnConstraint::Comment(comment) =
9738                                &col.constraints[comment_idx]
9739                            {
9740                                self.write_space();
9741                                self.write_keyword("COMMENT");
9742                                self.write_space();
9743                                self.generate_string_literal(comment)?;
9744                                comment_idx += 1;
9745                                break;
9746                            }
9747                            comment_idx += 1;
9748                        }
9749                    }
9750                    ConstraintType::Tags => {
9751                        // Find next Tags constraint (Snowflake)
9752                        for constraint in &col.constraints {
9753                            if let ColumnConstraint::Tags(tags) = constraint {
9754                                self.write_space();
9755                                self.write_keyword("TAG");
9756                                self.write(" (");
9757                                for (i, expr) in tags.expressions.iter().enumerate() {
9758                                    if i > 0 {
9759                                        self.write(", ");
9760                                    }
9761                                    self.generate_expression(expr)?;
9762                                }
9763                                self.write(")");
9764                                break;
9765                            }
9766                        }
9767                    }
9768                    ConstraintType::ComputedColumn => {
9769                        // Find next ComputedColumn constraint
9770                        for constraint in &col.constraints {
9771                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
9772                                self.write_space();
9773                                self.generate_computed_column_inline(cc)?;
9774                                break;
9775                            }
9776                        }
9777                    }
9778                    ConstraintType::GeneratedAsRow => {
9779                        // Find next GeneratedAsRow constraint
9780                        for constraint in &col.constraints {
9781                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
9782                                self.write_space();
9783                                self.generate_generated_as_row_inline(gar)?;
9784                                break;
9785                            }
9786                        }
9787                    }
9788                    ConstraintType::OnUpdate => {
9789                        if let Some(ref expr) = col.on_update {
9790                            self.write_space();
9791                            self.write_keyword("ON UPDATE");
9792                            self.write_space();
9793                            self.generate_expression(expr)?;
9794                        }
9795                    }
9796                    ConstraintType::Encode => {
9797                        if let Some(ref encoding) = col.encoding {
9798                            self.write_space();
9799                            self.write_keyword("ENCODE");
9800                            self.write_space();
9801                            self.write(encoding);
9802                        }
9803                    }
9804                    ConstraintType::Path => {
9805                        // Find next Path constraint
9806                        for constraint in &col.constraints {
9807                            if let ColumnConstraint::Path(path_expr) = constraint {
9808                                self.write_space();
9809                                self.write_keyword("PATH");
9810                                self.write_space();
9811                                self.generate_expression(path_expr)?;
9812                                break;
9813                            }
9814                        }
9815                    }
9816                }
9817            }
9818            if pending_not_null_after_identity {
9819                self.write_space();
9820                self.write_keyword("NOT NULL");
9821            }
9822        } else {
9823            // Legacy fixed order for backward compatibility
9824            if col.primary_key {
9825                self.write_space();
9826                self.write_keyword("PRIMARY KEY");
9827                if let Some(ref order) = col.primary_key_order {
9828                    self.write_space();
9829                    match order {
9830                        SortOrder::Asc => self.write_keyword("ASC"),
9831                        SortOrder::Desc => self.write_keyword("DESC"),
9832                    }
9833                }
9834            }
9835
9836            if col.unique {
9837                self.write_space();
9838                self.write_keyword("UNIQUE");
9839                // PostgreSQL 15+: NULLS NOT DISTINCT
9840                if col.unique_nulls_not_distinct {
9841                    self.write(" NULLS NOT DISTINCT");
9842                }
9843            }
9844
9845            match col.nullable {
9846                Some(false) => {
9847                    self.write_space();
9848                    self.write_keyword("NOT NULL");
9849                }
9850                Some(true) => {
9851                    self.write_space();
9852                    self.write_keyword("NULL");
9853                }
9854                None => {}
9855            }
9856
9857            if let Some(ref default) = col.default {
9858                self.write_space();
9859                self.write_keyword("DEFAULT");
9860                self.write_space();
9861                self.generate_expression(default)?;
9862            }
9863
9864            if col.auto_increment {
9865                self.write_space();
9866                self.generate_auto_increment_keyword(col)?;
9867            }
9868
9869            // Column-level constraints from Vec
9870            for constraint in &col.constraints {
9871                match constraint {
9872                    ColumnConstraint::References(fk_ref) => {
9873                        self.write_space();
9874                        if fk_ref.has_foreign_key_keywords {
9875                            self.write_keyword("FOREIGN KEY");
9876                            self.write_space();
9877                        }
9878                        self.write_keyword("REFERENCES");
9879                        self.write_space();
9880                        self.generate_table(&fk_ref.table)?;
9881                        if !fk_ref.columns.is_empty() {
9882                            self.write(" (");
9883                            for (i, c) in fk_ref.columns.iter().enumerate() {
9884                                if i > 0 {
9885                                    self.write(", ");
9886                                }
9887                                self.generate_identifier(c)?;
9888                            }
9889                            self.write(")");
9890                        }
9891                        self.generate_referential_actions(fk_ref)?;
9892                    }
9893                    ColumnConstraint::Check(expr) => {
9894                        self.write_space();
9895                        self.write_keyword("CHECK");
9896                        self.write(" (");
9897                        self.generate_expression(expr)?;
9898                        self.write(")");
9899                    }
9900                    ColumnConstraint::GeneratedAsIdentity(gen) => {
9901                        self.write_space();
9902                        // Redshift uses IDENTITY(start, increment) syntax
9903                        if matches!(
9904                            self.config.dialect,
9905                            Some(crate::dialects::DialectType::Redshift)
9906                        ) {
9907                            self.write_keyword("IDENTITY");
9908                            self.write("(");
9909                            if let Some(ref start) = gen.start {
9910                                self.generate_expression(start)?;
9911                            } else {
9912                                self.write("0");
9913                            }
9914                            self.write(", ");
9915                            if let Some(ref incr) = gen.increment {
9916                                self.generate_expression(incr)?;
9917                            } else {
9918                                self.write("1");
9919                            }
9920                            self.write(")");
9921                        } else {
9922                            self.write_keyword("GENERATED");
9923                            if gen.always {
9924                                self.write_space();
9925                                self.write_keyword("ALWAYS");
9926                            } else {
9927                                self.write_space();
9928                                self.write_keyword("BY DEFAULT");
9929                                if gen.on_null {
9930                                    self.write_space();
9931                                    self.write_keyword("ON NULL");
9932                                }
9933                            }
9934                            self.write_space();
9935                            self.write_keyword("AS IDENTITY");
9936
9937                            let has_options = gen.start.is_some()
9938                                || gen.increment.is_some()
9939                                || gen.minvalue.is_some()
9940                                || gen.maxvalue.is_some()
9941                                || gen.cycle.is_some();
9942                            if has_options {
9943                                self.write(" (");
9944                                let mut first = true;
9945                                if let Some(ref start) = gen.start {
9946                                    if !first {
9947                                        self.write(" ");
9948                                    }
9949                                    first = false;
9950                                    self.write_keyword("START WITH");
9951                                    self.write_space();
9952                                    self.generate_expression(start)?;
9953                                }
9954                                if let Some(ref incr) = gen.increment {
9955                                    if !first {
9956                                        self.write(" ");
9957                                    }
9958                                    first = false;
9959                                    self.write_keyword("INCREMENT BY");
9960                                    self.write_space();
9961                                    self.generate_expression(incr)?;
9962                                }
9963                                if let Some(ref minv) = gen.minvalue {
9964                                    if !first {
9965                                        self.write(" ");
9966                                    }
9967                                    first = false;
9968                                    self.write_keyword("MINVALUE");
9969                                    self.write_space();
9970                                    self.generate_expression(minv)?;
9971                                }
9972                                if let Some(ref maxv) = gen.maxvalue {
9973                                    if !first {
9974                                        self.write(" ");
9975                                    }
9976                                    first = false;
9977                                    self.write_keyword("MAXVALUE");
9978                                    self.write_space();
9979                                    self.generate_expression(maxv)?;
9980                                }
9981                                if let Some(cycle) = gen.cycle {
9982                                    if !first {
9983                                        self.write(" ");
9984                                    }
9985                                    if cycle {
9986                                        self.write_keyword("CYCLE");
9987                                    } else {
9988                                        self.write_keyword("NO CYCLE");
9989                                    }
9990                                }
9991                                self.write(")");
9992                            }
9993                        }
9994                    }
9995                    ColumnConstraint::Collate(collation) => {
9996                        self.write_space();
9997                        self.write_keyword("COLLATE");
9998                        self.write_space();
9999                        self.generate_identifier(collation)?;
10000                    }
10001                    ColumnConstraint::Comment(comment) => {
10002                        self.write_space();
10003                        self.write_keyword("COMMENT");
10004                        self.write_space();
10005                        self.generate_string_literal(comment)?;
10006                    }
10007                    ColumnConstraint::Path(path_expr) => {
10008                        self.write_space();
10009                        self.write_keyword("PATH");
10010                        self.write_space();
10011                        self.generate_expression(path_expr)?;
10012                    }
10013                    _ => {} // Other constraints handled above
10014                }
10015            }
10016
10017            // Redshift: ENCODE encoding_type (legacy path)
10018            if let Some(ref encoding) = col.encoding {
10019                self.write_space();
10020                self.write_keyword("ENCODE");
10021                self.write_space();
10022                self.write(encoding);
10023            }
10024        }
10025
10026        // ClickHouse: CODEC(...)
10027        if let Some(ref codec) = col.codec {
10028            self.write_space();
10029            self.write_keyword("CODEC");
10030            self.write("(");
10031            self.write(codec);
10032            self.write(")");
10033        }
10034
10035        if let Some(visible) = col.visible {
10036            self.write_space();
10037            if visible {
10038                self.write_keyword("VISIBLE");
10039            } else {
10040                self.write_keyword("INVISIBLE");
10041            }
10042        }
10043
10044        // ClickHouse: EPHEMERAL [expr]
10045        if let Some(ref ephemeral) = col.ephemeral {
10046            self.write_space();
10047            self.write_keyword("EPHEMERAL");
10048            if let Some(ref expr) = ephemeral {
10049                self.write_space();
10050                self.generate_expression(expr)?;
10051            }
10052        }
10053
10054        // ClickHouse: MATERIALIZED expr
10055        if let Some(ref mat_expr) = col.materialized_expr {
10056            self.write_space();
10057            self.write_keyword("MATERIALIZED");
10058            self.write_space();
10059            self.generate_expression(mat_expr)?;
10060        }
10061
10062        // ClickHouse: ALIAS expr
10063        if let Some(ref alias_expr) = col.alias_expr {
10064            self.write_space();
10065            self.write_keyword("ALIAS");
10066            self.write_space();
10067            self.generate_expression(alias_expr)?;
10068        }
10069
10070        // ClickHouse: TTL expr
10071        if let Some(ref ttl_expr) = col.ttl_expr {
10072            self.write_space();
10073            self.write_keyword("TTL");
10074            self.write_space();
10075            self.generate_expression(ttl_expr)?;
10076        }
10077
10078        // TSQL: NOT FOR REPLICATION
10079        if col.not_for_replication
10080            && matches!(
10081                self.config.dialect,
10082                Some(crate::dialects::DialectType::TSQL)
10083                    | Some(crate::dialects::DialectType::Fabric)
10084            )
10085        {
10086            self.write_space();
10087            self.write_keyword("NOT FOR REPLICATION");
10088        }
10089
10090        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
10091        if !col.options.is_empty() {
10092            self.write_space();
10093            self.generate_options_clause(&col.options)?;
10094        }
10095
10096        // SQLite: Inline PRIMARY KEY from table constraint
10097        // This comes at the end, after all existing column constraints
10098        if !col.primary_key
10099            && self
10100                .sqlite_inline_pk_columns
10101                .contains(&col.name.name.to_ascii_lowercase())
10102        {
10103            self.write_space();
10104            self.write_keyword("PRIMARY KEY");
10105            if matches!(self.config.dialect, Some(DialectType::SQLite)) && col.auto_increment {
10106                self.write_space();
10107                self.generate_auto_increment_keyword(col)?;
10108            }
10109        }
10110
10111        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
10112        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
10113        if serial_expansion.is_some() {
10114            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
10115                self.write_space();
10116                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
10117            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
10118                self.write_space();
10119                self.write_keyword("NOT NULL");
10120            }
10121        }
10122
10123        Ok(())
10124    }
10125
10126    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
10127        match constraint {
10128            TableConstraint::PrimaryKey {
10129                name,
10130                columns,
10131                include_columns,
10132                modifiers,
10133                has_constraint_keyword,
10134            } => {
10135                if let Some(ref n) = name {
10136                    if *has_constraint_keyword {
10137                        self.write_keyword("CONSTRAINT");
10138                        self.write_space();
10139                        self.generate_identifier(n)?;
10140                        self.write_space();
10141                    }
10142                }
10143                self.write_keyword("PRIMARY KEY");
10144                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
10145                if let Some(ref clustered) = modifiers.clustered {
10146                    self.write_space();
10147                    self.write_keyword(clustered);
10148                }
10149                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
10150                if let Some(ref n) = name {
10151                    if !*has_constraint_keyword {
10152                        self.write_space();
10153                        self.generate_identifier(n)?;
10154                    }
10155                }
10156                self.write(" (");
10157                for (i, col) in columns.iter().enumerate() {
10158                    if i > 0 {
10159                        self.write(", ");
10160                    }
10161                    self.generate_identifier(col)?;
10162                }
10163                self.write(")");
10164                if !include_columns.is_empty() {
10165                    self.write_space();
10166                    self.write_keyword("INCLUDE");
10167                    self.write(" (");
10168                    for (i, col) in include_columns.iter().enumerate() {
10169                        if i > 0 {
10170                            self.write(", ");
10171                        }
10172                        self.generate_identifier(col)?;
10173                    }
10174                    self.write(")");
10175                }
10176                self.generate_constraint_modifiers(modifiers);
10177            }
10178            TableConstraint::Unique {
10179                name,
10180                columns,
10181                columns_parenthesized,
10182                modifiers,
10183                has_constraint_keyword,
10184                nulls_not_distinct,
10185            } => {
10186                if let Some(ref n) = name {
10187                    if *has_constraint_keyword {
10188                        self.write_keyword("CONSTRAINT");
10189                        self.write_space();
10190                        self.generate_identifier(n)?;
10191                        self.write_space();
10192                    }
10193                }
10194                self.write_keyword("UNIQUE");
10195                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
10196                if let Some(ref clustered) = modifiers.clustered {
10197                    self.write_space();
10198                    self.write_keyword(clustered);
10199                }
10200                // PostgreSQL 15+: NULLS NOT DISTINCT
10201                if *nulls_not_distinct {
10202                    self.write(" NULLS NOT DISTINCT");
10203                }
10204                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
10205                if let Some(ref n) = name {
10206                    if !*has_constraint_keyword {
10207                        self.write_space();
10208                        self.generate_identifier(n)?;
10209                    }
10210                }
10211                if *columns_parenthesized {
10212                    self.write(" (");
10213                    for (i, col) in columns.iter().enumerate() {
10214                        if i > 0 {
10215                            self.write(", ");
10216                        }
10217                        self.generate_identifier(col)?;
10218                    }
10219                    self.write(")");
10220                } else {
10221                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
10222                    for col in columns.iter() {
10223                        self.write_space();
10224                        self.generate_identifier(col)?;
10225                    }
10226                }
10227                self.generate_constraint_modifiers(modifiers);
10228            }
10229            TableConstraint::ForeignKey {
10230                name,
10231                columns,
10232                references,
10233                on_delete,
10234                on_update,
10235                modifiers,
10236            } => {
10237                if let Some(ref n) = name {
10238                    self.write_keyword("CONSTRAINT");
10239                    self.write_space();
10240                    self.generate_identifier(n)?;
10241                    self.write_space();
10242                }
10243                self.write_keyword("FOREIGN KEY");
10244                self.write(" (");
10245                for (i, col) in columns.iter().enumerate() {
10246                    if i > 0 {
10247                        self.write(", ");
10248                    }
10249                    self.generate_identifier(col)?;
10250                }
10251                self.write(")");
10252                if let Some(ref refs) = references {
10253                    self.write(" ");
10254                    self.write_keyword("REFERENCES");
10255                    self.write_space();
10256                    self.generate_table(&refs.table)?;
10257                    if !refs.columns.is_empty() {
10258                        if self.config.pretty {
10259                            self.write(" (");
10260                            self.write_newline();
10261                            self.indent_level += 1;
10262                            for (i, col) in refs.columns.iter().enumerate() {
10263                                if i > 0 {
10264                                    self.write(",");
10265                                    self.write_newline();
10266                                }
10267                                self.write_indent();
10268                                self.generate_identifier(col)?;
10269                            }
10270                            self.indent_level -= 1;
10271                            self.write_newline();
10272                            self.write_indent();
10273                            self.write(")");
10274                        } else {
10275                            self.write(" (");
10276                            for (i, col) in refs.columns.iter().enumerate() {
10277                                if i > 0 {
10278                                    self.write(", ");
10279                                }
10280                                self.generate_identifier(col)?;
10281                            }
10282                            self.write(")");
10283                        }
10284                    }
10285                    self.generate_referential_actions(refs)?;
10286                } else {
10287                    // No REFERENCES - output ON DELETE/ON UPDATE directly
10288                    if let Some(ref action) = on_delete {
10289                        self.write_space();
10290                        self.write_keyword("ON DELETE");
10291                        self.write_space();
10292                        self.generate_referential_action(action);
10293                    }
10294                    if let Some(ref action) = on_update {
10295                        self.write_space();
10296                        self.write_keyword("ON UPDATE");
10297                        self.write_space();
10298                        self.generate_referential_action(action);
10299                    }
10300                }
10301                self.generate_constraint_modifiers(modifiers);
10302            }
10303            TableConstraint::Check {
10304                name,
10305                expression,
10306                modifiers,
10307            } => {
10308                if let Some(ref n) = name {
10309                    self.write_keyword("CONSTRAINT");
10310                    self.write_space();
10311                    self.generate_identifier(n)?;
10312                    self.write_space();
10313                }
10314                self.write_keyword("CHECK");
10315                self.write(" (");
10316                self.generate_expression(expression)?;
10317                self.write(")");
10318                self.generate_constraint_modifiers(modifiers);
10319            }
10320            TableConstraint::Assume { name, expression } => {
10321                if let Some(ref n) = name {
10322                    self.write_keyword("CONSTRAINT");
10323                    self.write_space();
10324                    self.generate_identifier(n)?;
10325                    self.write_space();
10326                }
10327                self.write_keyword("ASSUME");
10328                self.write(" (");
10329                self.generate_expression(expression)?;
10330                self.write(")");
10331            }
10332            TableConstraint::Default {
10333                name,
10334                expression,
10335                column,
10336            } => {
10337                if let Some(ref n) = name {
10338                    self.write_keyword("CONSTRAINT");
10339                    self.write_space();
10340                    self.generate_identifier(n)?;
10341                    self.write_space();
10342                }
10343                self.write_keyword("DEFAULT");
10344                self.write_space();
10345                self.generate_expression(expression)?;
10346                self.write_space();
10347                self.write_keyword("FOR");
10348                self.write_space();
10349                self.generate_identifier(column)?;
10350            }
10351            TableConstraint::Index {
10352                name,
10353                columns,
10354                kind,
10355                modifiers,
10356                use_key_keyword,
10357                expression,
10358                index_type,
10359                granularity,
10360            } => {
10361                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
10362                if expression.is_some() {
10363                    self.write_keyword("INDEX");
10364                    if let Some(ref n) = name {
10365                        self.write_space();
10366                        self.generate_identifier(n)?;
10367                    }
10368                    if let Some(ref expr) = expression {
10369                        self.write_space();
10370                        self.generate_expression(expr)?;
10371                    }
10372                    if let Some(ref idx_type) = index_type {
10373                        self.write_space();
10374                        self.write_keyword("TYPE");
10375                        self.write_space();
10376                        self.generate_expression(idx_type)?;
10377                    }
10378                    if let Some(ref gran) = granularity {
10379                        self.write_space();
10380                        self.write_keyword("GRANULARITY");
10381                        self.write_space();
10382                        self.generate_expression(gran)?;
10383                    }
10384                } else {
10385                    // Standard INDEX syntax
10386                    // Determine the index keyword to use
10387                    // MySQL normalizes KEY to INDEX
10388                    use crate::dialects::DialectType;
10389                    let index_keyword = if *use_key_keyword
10390                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
10391                    {
10392                        "KEY"
10393                    } else {
10394                        "INDEX"
10395                    };
10396
10397                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
10398                    if let Some(ref k) = kind {
10399                        self.write_keyword(k);
10400                        // For UNIQUE, don't add INDEX/KEY keyword
10401                        if k != "UNIQUE" {
10402                            self.write_space();
10403                            self.write_keyword(index_keyword);
10404                        }
10405                    } else {
10406                        self.write_keyword(index_keyword);
10407                    }
10408
10409                    // Output USING before name if using_before_columns is true and there's no name
10410                    if modifiers.using_before_columns && name.is_none() {
10411                        if let Some(ref using) = modifiers.using {
10412                            self.write_space();
10413                            self.write_keyword("USING");
10414                            self.write_space();
10415                            self.write_keyword(using);
10416                        }
10417                    }
10418
10419                    // Output index name if present
10420                    if let Some(ref n) = name {
10421                        self.write_space();
10422                        self.generate_identifier(n)?;
10423                    }
10424
10425                    // Output USING after name but before columns if using_before_columns and there's a name
10426                    if modifiers.using_before_columns && name.is_some() {
10427                        if let Some(ref using) = modifiers.using {
10428                            self.write_space();
10429                            self.write_keyword("USING");
10430                            self.write_space();
10431                            self.write_keyword(using);
10432                        }
10433                    }
10434
10435                    // Output columns
10436                    self.write(" (");
10437                    for (i, col) in columns.iter().enumerate() {
10438                        if i > 0 {
10439                            self.write(", ");
10440                        }
10441                        self.generate_identifier(col)?;
10442                    }
10443                    self.write(")");
10444
10445                    // Output USING after columns if not using_before_columns
10446                    if !modifiers.using_before_columns {
10447                        if let Some(ref using) = modifiers.using {
10448                            self.write_space();
10449                            self.write_keyword("USING");
10450                            self.write_space();
10451                            self.write_keyword(using);
10452                        }
10453                    }
10454
10455                    // Output other constraint modifiers (but skip USING since we already handled it)
10456                    self.generate_constraint_modifiers_without_using(modifiers);
10457                }
10458            }
10459            TableConstraint::Projection { name, expression } => {
10460                // ClickHouse: PROJECTION name (SELECT ...)
10461                self.write_keyword("PROJECTION");
10462                self.write_space();
10463                self.generate_identifier(name)?;
10464                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
10465                    if let Expression::Raw(raw) = expression {
10466                        if raw
10467                            .sql
10468                            .trim_start()
10469                            .to_ascii_uppercase()
10470                            .starts_with("INDEX ")
10471                        {
10472                            self.write_space();
10473                            self.write(raw.sql.trim());
10474                            return Ok(());
10475                        }
10476                    }
10477                }
10478                self.write(" (");
10479                self.generate_expression(expression)?;
10480                self.write(")");
10481            }
10482            TableConstraint::Like { source, options } => {
10483                self.write_keyword("LIKE");
10484                self.write_space();
10485                self.generate_table(source)?;
10486                for (action, prop) in options {
10487                    self.write_space();
10488                    match action {
10489                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
10490                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
10491                    }
10492                    self.write_space();
10493                    self.write_keyword(prop);
10494                }
10495            }
10496            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
10497                self.write_keyword("PERIOD FOR SYSTEM_TIME");
10498                self.write(" (");
10499                self.generate_identifier(start_col)?;
10500                self.write(", ");
10501                self.generate_identifier(end_col)?;
10502                self.write(")");
10503            }
10504            TableConstraint::Exclude {
10505                name,
10506                using,
10507                elements,
10508                include_columns,
10509                where_clause,
10510                with_params,
10511                using_index_tablespace,
10512                modifiers: _,
10513            } => {
10514                if let Some(ref n) = name {
10515                    self.write_keyword("CONSTRAINT");
10516                    self.write_space();
10517                    self.generate_identifier(n)?;
10518                    self.write_space();
10519                }
10520                self.write_keyword("EXCLUDE");
10521                if let Some(ref method) = using {
10522                    self.write_space();
10523                    self.write_keyword("USING");
10524                    self.write_space();
10525                    self.write(method);
10526                    self.write("(");
10527                } else {
10528                    self.write(" (");
10529                }
10530                for (i, elem) in elements.iter().enumerate() {
10531                    if i > 0 {
10532                        self.write(", ");
10533                    }
10534                    self.write(&elem.expression);
10535                    self.write_space();
10536                    self.write_keyword("WITH");
10537                    self.write_space();
10538                    self.write(&elem.operator);
10539                }
10540                self.write(")");
10541                if !include_columns.is_empty() {
10542                    self.write_space();
10543                    self.write_keyword("INCLUDE");
10544                    self.write(" (");
10545                    for (i, col) in include_columns.iter().enumerate() {
10546                        if i > 0 {
10547                            self.write(", ");
10548                        }
10549                        self.generate_identifier(col)?;
10550                    }
10551                    self.write(")");
10552                }
10553                if !with_params.is_empty() {
10554                    self.write_space();
10555                    self.write_keyword("WITH");
10556                    self.write(" (");
10557                    for (i, (key, val)) in with_params.iter().enumerate() {
10558                        if i > 0 {
10559                            self.write(", ");
10560                        }
10561                        self.write(key);
10562                        self.write("=");
10563                        self.write(val);
10564                    }
10565                    self.write(")");
10566                }
10567                if let Some(ref tablespace) = using_index_tablespace {
10568                    self.write_space();
10569                    self.write_keyword("USING INDEX TABLESPACE");
10570                    self.write_space();
10571                    self.write(tablespace);
10572                }
10573                if let Some(ref where_expr) = where_clause {
10574                    self.write_space();
10575                    self.write_keyword("WHERE");
10576                    self.write(" (");
10577                    self.generate_expression(where_expr)?;
10578                    self.write(")");
10579                }
10580            }
10581            TableConstraint::Tags(tags) => {
10582                self.write_keyword("TAG");
10583                self.write(" (");
10584                for (i, expr) in tags.expressions.iter().enumerate() {
10585                    if i > 0 {
10586                        self.write(", ");
10587                    }
10588                    self.generate_expression(expr)?;
10589                }
10590                self.write(")");
10591            }
10592            TableConstraint::InitiallyDeferred { deferred } => {
10593                self.write_keyword("INITIALLY");
10594                self.write_space();
10595                if *deferred {
10596                    self.write_keyword("DEFERRED");
10597                } else {
10598                    self.write_keyword("IMMEDIATE");
10599                }
10600            }
10601        }
10602        Ok(())
10603    }
10604
10605    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
10606        // Output USING BTREE/HASH (MySQL) - comes first
10607        if let Some(using) = &modifiers.using {
10608            self.write_space();
10609            self.write_keyword("USING");
10610            self.write_space();
10611            self.write_keyword(using);
10612        }
10613        // Output ENFORCED/NOT ENFORCED
10614        if let Some(enforced) = modifiers.enforced {
10615            self.write_space();
10616            if enforced {
10617                self.write_keyword("ENFORCED");
10618            } else {
10619                self.write_keyword("NOT ENFORCED");
10620            }
10621        }
10622        // Output DEFERRABLE/NOT DEFERRABLE
10623        if let Some(deferrable) = modifiers.deferrable {
10624            self.write_space();
10625            if deferrable {
10626                self.write_keyword("DEFERRABLE");
10627            } else {
10628                self.write_keyword("NOT DEFERRABLE");
10629            }
10630        }
10631        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
10632        if let Some(initially_deferred) = modifiers.initially_deferred {
10633            self.write_space();
10634            if initially_deferred {
10635                self.write_keyword("INITIALLY DEFERRED");
10636            } else {
10637                self.write_keyword("INITIALLY IMMEDIATE");
10638            }
10639        }
10640        // Output NORELY
10641        if modifiers.norely {
10642            self.write_space();
10643            self.write_keyword("NORELY");
10644        }
10645        // Output RELY
10646        if modifiers.rely {
10647            self.write_space();
10648            self.write_keyword("RELY");
10649        }
10650        // Output NOT VALID (PostgreSQL)
10651        if modifiers.not_valid {
10652            self.write_space();
10653            self.write_keyword("NOT VALID");
10654        }
10655        // Output ON CONFLICT (SQLite)
10656        if let Some(on_conflict) = &modifiers.on_conflict {
10657            self.write_space();
10658            self.write_keyword("ON CONFLICT");
10659            self.write_space();
10660            self.write_keyword(on_conflict);
10661        }
10662        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
10663        if !modifiers.with_options.is_empty() {
10664            self.write_space();
10665            self.write_keyword("WITH");
10666            self.write(" (");
10667            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
10668                if i > 0 {
10669                    self.write(", ");
10670                }
10671                self.write(key);
10672                self.write("=");
10673                self.write(value);
10674            }
10675            self.write(")");
10676        }
10677        // Output TSQL ON filegroup
10678        if let Some(ref fg) = modifiers.on_filegroup {
10679            self.write_space();
10680            self.write_keyword("ON");
10681            self.write_space();
10682            let _ = self.generate_identifier(fg);
10683        }
10684    }
10685
10686    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
10687    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
10688        // Output ENFORCED/NOT ENFORCED
10689        if let Some(enforced) = modifiers.enforced {
10690            self.write_space();
10691            if enforced {
10692                self.write_keyword("ENFORCED");
10693            } else {
10694                self.write_keyword("NOT ENFORCED");
10695            }
10696        }
10697        // Output DEFERRABLE/NOT DEFERRABLE
10698        if let Some(deferrable) = modifiers.deferrable {
10699            self.write_space();
10700            if deferrable {
10701                self.write_keyword("DEFERRABLE");
10702            } else {
10703                self.write_keyword("NOT DEFERRABLE");
10704            }
10705        }
10706        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
10707        if let Some(initially_deferred) = modifiers.initially_deferred {
10708            self.write_space();
10709            if initially_deferred {
10710                self.write_keyword("INITIALLY DEFERRED");
10711            } else {
10712                self.write_keyword("INITIALLY IMMEDIATE");
10713            }
10714        }
10715        // Output NORELY
10716        if modifiers.norely {
10717            self.write_space();
10718            self.write_keyword("NORELY");
10719        }
10720        // Output RELY
10721        if modifiers.rely {
10722            self.write_space();
10723            self.write_keyword("RELY");
10724        }
10725        // Output NOT VALID (PostgreSQL)
10726        if modifiers.not_valid {
10727            self.write_space();
10728            self.write_keyword("NOT VALID");
10729        }
10730        // Output ON CONFLICT (SQLite)
10731        if let Some(on_conflict) = &modifiers.on_conflict {
10732            self.write_space();
10733            self.write_keyword("ON CONFLICT");
10734            self.write_space();
10735            self.write_keyword(on_conflict);
10736        }
10737        // Output MySQL index-specific modifiers
10738        self.generate_index_specific_modifiers(modifiers);
10739    }
10740
10741    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
10742    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
10743        if let Some(ref comment) = modifiers.comment {
10744            self.write_space();
10745            self.write_keyword("COMMENT");
10746            self.write(" '");
10747            self.write(comment);
10748            self.write("'");
10749        }
10750        if let Some(visible) = modifiers.visible {
10751            self.write_space();
10752            if visible {
10753                self.write_keyword("VISIBLE");
10754            } else {
10755                self.write_keyword("INVISIBLE");
10756            }
10757        }
10758        if let Some(ref attr) = modifiers.engine_attribute {
10759            self.write_space();
10760            self.write_keyword("ENGINE_ATTRIBUTE");
10761            self.write(" = '");
10762            self.write(attr);
10763            self.write("'");
10764        }
10765        if let Some(ref parser) = modifiers.with_parser {
10766            self.write_space();
10767            self.write_keyword("WITH PARSER");
10768            self.write_space();
10769            self.write(parser);
10770        }
10771    }
10772
10773    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
10774        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
10775        if !fk_ref.match_after_actions {
10776            if let Some(ref match_type) = fk_ref.match_type {
10777                self.write_space();
10778                self.write_keyword("MATCH");
10779                self.write_space();
10780                match match_type {
10781                    MatchType::Full => self.write_keyword("FULL"),
10782                    MatchType::Partial => self.write_keyword("PARTIAL"),
10783                    MatchType::Simple => self.write_keyword("SIMPLE"),
10784                }
10785            }
10786        }
10787
10788        // Output ON UPDATE and ON DELETE in the original order
10789        if fk_ref.on_update_first {
10790            if let Some(ref action) = fk_ref.on_update {
10791                self.write_space();
10792                self.write_keyword("ON UPDATE");
10793                self.write_space();
10794                self.generate_referential_action(action);
10795            }
10796            if let Some(ref action) = fk_ref.on_delete {
10797                self.write_space();
10798                self.write_keyword("ON DELETE");
10799                self.write_space();
10800                self.generate_referential_action(action);
10801            }
10802        } else {
10803            if let Some(ref action) = fk_ref.on_delete {
10804                self.write_space();
10805                self.write_keyword("ON DELETE");
10806                self.write_space();
10807                self.generate_referential_action(action);
10808            }
10809            if let Some(ref action) = fk_ref.on_update {
10810                self.write_space();
10811                self.write_keyword("ON UPDATE");
10812                self.write_space();
10813                self.generate_referential_action(action);
10814            }
10815        }
10816
10817        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
10818        if fk_ref.match_after_actions {
10819            if let Some(ref match_type) = fk_ref.match_type {
10820                self.write_space();
10821                self.write_keyword("MATCH");
10822                self.write_space();
10823                match match_type {
10824                    MatchType::Full => self.write_keyword("FULL"),
10825                    MatchType::Partial => self.write_keyword("PARTIAL"),
10826                    MatchType::Simple => self.write_keyword("SIMPLE"),
10827                }
10828            }
10829        }
10830
10831        // DEFERRABLE / NOT DEFERRABLE
10832        if let Some(deferrable) = fk_ref.deferrable {
10833            self.write_space();
10834            if deferrable {
10835                self.write_keyword("DEFERRABLE");
10836            } else {
10837                self.write_keyword("NOT DEFERRABLE");
10838            }
10839        }
10840
10841        Ok(())
10842    }
10843
10844    fn generate_referential_action(&mut self, action: &ReferentialAction) {
10845        match action {
10846            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
10847            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
10848            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
10849            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
10850            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
10851        }
10852    }
10853
10854    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
10855        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
10856        if let Some(ref object_id_args) = dt.object_id_args {
10857            if matches!(
10858                self.config.dialect,
10859                Some(crate::dialects::DialectType::TSQL)
10860                    | Some(crate::dialects::DialectType::Fabric)
10861            ) {
10862                self.write_keyword("IF NOT OBJECT_ID");
10863                self.write("(");
10864                self.write(object_id_args);
10865                self.write(")");
10866                self.write_space();
10867                self.write_keyword("IS NULL BEGIN DROP TABLE");
10868                self.write_space();
10869                for (i, table) in dt.names.iter().enumerate() {
10870                    if i > 0 {
10871                        self.write(", ");
10872                    }
10873                    self.generate_table(table)?;
10874                }
10875                self.write("; ");
10876                self.write_keyword("END");
10877                return Ok(());
10878            }
10879        }
10880
10881        // Athena: DROP TABLE uses Hive engine (backticks)
10882        let saved_athena_hive_context = self.athena_hive_context;
10883        if matches!(
10884            self.config.dialect,
10885            Some(crate::dialects::DialectType::Athena)
10886        ) {
10887            self.athena_hive_context = true;
10888        }
10889
10890        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
10891        for comment in &dt.leading_comments {
10892            self.write_formatted_comment(comment);
10893            self.write_space();
10894        }
10895        if dt.iceberg {
10896            self.write_keyword("DROP ICEBERG TABLE");
10897        } else {
10898            self.write_keyword("DROP TABLE");
10899        }
10900
10901        if dt.if_exists {
10902            self.write_space();
10903            self.write_keyword("IF EXISTS");
10904        }
10905
10906        self.write_space();
10907        for (i, table) in dt.names.iter().enumerate() {
10908            if i > 0 {
10909                self.write(", ");
10910            }
10911            self.generate_table(table)?;
10912        }
10913
10914        if dt.cascade_constraints {
10915            self.write_space();
10916            self.write_keyword("CASCADE CONSTRAINTS");
10917        } else if dt.cascade {
10918            self.write_space();
10919            self.write_keyword("CASCADE");
10920        }
10921
10922        if dt.restrict {
10923            self.write_space();
10924            self.write_keyword("RESTRICT");
10925        }
10926
10927        if dt.purge {
10928            self.write_space();
10929            self.write_keyword("PURGE");
10930        }
10931
10932        if dt.sync {
10933            self.write_space();
10934            self.write_keyword("SYNC");
10935        }
10936
10937        // Restore Athena Hive context
10938        self.athena_hive_context = saved_athena_hive_context;
10939
10940        Ok(())
10941    }
10942
10943    fn generate_undrop(&mut self, u: &Undrop) -> Result<()> {
10944        self.write_keyword("UNDROP");
10945        self.write_space();
10946        self.write_keyword(&u.kind);
10947        if u.if_exists {
10948            self.write_space();
10949            self.write_keyword("IF EXISTS");
10950        }
10951        self.write_space();
10952        self.generate_table(&u.name)?;
10953        if let Some(rename_to) = &u.rename_to {
10954            self.write_space();
10955            self.write_keyword("RENAME TO");
10956            self.write_space();
10957            self.generate_table(rename_to)?;
10958        }
10959        Ok(())
10960    }
10961
10962    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
10963        // Athena: ALTER TABLE uses Hive engine (backticks)
10964        let saved_athena_hive_context = self.athena_hive_context;
10965        if matches!(
10966            self.config.dialect,
10967            Some(crate::dialects::DialectType::Athena)
10968        ) {
10969            self.athena_hive_context = true;
10970        }
10971
10972        self.write_keyword("ALTER");
10973        // Write table modifier (e.g., ICEBERG) unless target is DuckDB
10974        if let Some(ref modifier) = at.table_modifier {
10975            if !matches!(
10976                self.config.dialect,
10977                Some(crate::dialects::DialectType::DuckDB)
10978            ) {
10979                self.write_space();
10980                self.write_keyword(modifier);
10981            }
10982        }
10983        self.write(" ");
10984        self.write_keyword("TABLE");
10985        if at.if_exists {
10986            self.write_space();
10987            self.write_keyword("IF EXISTS");
10988        }
10989        self.write_space();
10990        self.generate_table(&at.name)?;
10991
10992        // ClickHouse: ON CLUSTER clause
10993        if let Some(ref on_cluster) = at.on_cluster {
10994            self.write_space();
10995            self.generate_on_cluster(on_cluster)?;
10996        }
10997
10998        // Hive: PARTITION(key=value, ...) clause
10999        if let Some(ref partition) = at.partition {
11000            self.write_space();
11001            self.write_keyword("PARTITION");
11002            self.write("(");
11003            for (i, (key, value)) in partition.iter().enumerate() {
11004                if i > 0 {
11005                    self.write(", ");
11006                }
11007                self.generate_identifier(key)?;
11008                self.write(" = ");
11009                self.generate_expression(value)?;
11010            }
11011            self.write(")");
11012        }
11013
11014        // TSQL: WITH CHECK / WITH NOCHECK modifier
11015        if let Some(ref with_check) = at.with_check {
11016            self.write_space();
11017            self.write_keyword(with_check);
11018        }
11019
11020        if self.config.pretty {
11021            // In pretty mode, format actions with newlines and indentation
11022            self.write_newline();
11023            self.indent_level += 1;
11024            for (i, action) in at.actions.iter().enumerate() {
11025                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
11026                let is_continuation = i > 0
11027                    && matches!(
11028                        (&at.actions[i - 1], action),
11029                        (
11030                            AlterTableAction::AddColumn { .. },
11031                            AlterTableAction::AddColumn { .. }
11032                        ) | (
11033                            AlterTableAction::AddConstraint(_),
11034                            AlterTableAction::AddConstraint(_)
11035                        )
11036                    );
11037                if i > 0 {
11038                    self.write(",");
11039                    self.write_newline();
11040                }
11041                self.write_indent();
11042                self.generate_alter_action_with_continuation(action, is_continuation)?;
11043            }
11044            self.indent_level -= 1;
11045        } else {
11046            for (i, action) in at.actions.iter().enumerate() {
11047                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
11048                let is_continuation = i > 0
11049                    && matches!(
11050                        (&at.actions[i - 1], action),
11051                        (
11052                            AlterTableAction::AddColumn { .. },
11053                            AlterTableAction::AddColumn { .. }
11054                        ) | (
11055                            AlterTableAction::AddConstraint(_),
11056                            AlterTableAction::AddConstraint(_)
11057                        )
11058                    );
11059                if i > 0 {
11060                    self.write(",");
11061                }
11062                self.write_space();
11063                self.generate_alter_action_with_continuation(action, is_continuation)?;
11064            }
11065        }
11066
11067        // MySQL ALTER TABLE trailing options
11068        if let Some(ref algorithm) = at.algorithm {
11069            self.write(", ");
11070            self.write_keyword("ALGORITHM");
11071            self.write("=");
11072            self.write_keyword(algorithm);
11073        }
11074        if let Some(ref lock) = at.lock {
11075            self.write(", ");
11076            self.write_keyword("LOCK");
11077            self.write("=");
11078            self.write_keyword(lock);
11079        }
11080
11081        // Restore Athena Hive context
11082        self.athena_hive_context = saved_athena_hive_context;
11083
11084        Ok(())
11085    }
11086
11087    fn generate_alter_action_with_continuation(
11088        &mut self,
11089        action: &AlterTableAction,
11090        is_continuation: bool,
11091    ) -> Result<()> {
11092        match action {
11093            AlterTableAction::AddColumn {
11094                column,
11095                if_not_exists,
11096                position,
11097            } => {
11098                use crate::dialects::DialectType;
11099                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
11100                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
11101                // For other dialects, repeat ADD COLUMN for each
11102                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
11103                let is_tsql_like = matches!(
11104                    self.config.dialect,
11105                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
11106                );
11107                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
11108                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
11109
11110                if is_continuation && (is_snowflake || is_tsql_like) {
11111                    // Don't write ADD keyword for continuation in Snowflake/TSQL
11112                } else if is_snowflake {
11113                    self.write_keyword("ADD");
11114                    self.write_space();
11115                } else if is_athena {
11116                    // Athena uses ADD COLUMNS (col_def) syntax
11117                    self.write_keyword("ADD COLUMNS");
11118                    self.write(" (");
11119                } else if self.config.alter_table_include_column_keyword {
11120                    self.write_keyword("ADD COLUMN");
11121                    self.write_space();
11122                } else {
11123                    // Dialects like Oracle and TSQL don't use COLUMN keyword
11124                    self.write_keyword("ADD");
11125                    self.write_space();
11126                }
11127
11128                if *if_not_exists {
11129                    self.write_keyword("IF NOT EXISTS");
11130                    self.write_space();
11131                }
11132                self.generate_column_def(column)?;
11133
11134                // Close parenthesis for Athena
11135                if is_athena {
11136                    self.write(")");
11137                }
11138
11139                // Column position (FIRST or AFTER)
11140                if let Some(pos) = position {
11141                    self.write_space();
11142                    match pos {
11143                        ColumnPosition::First => self.write_keyword("FIRST"),
11144                        ColumnPosition::After(col_name) => {
11145                            self.write_keyword("AFTER");
11146                            self.write_space();
11147                            self.generate_identifier(col_name)?;
11148                        }
11149                    }
11150                }
11151            }
11152            AlterTableAction::DropColumn {
11153                name,
11154                if_exists,
11155                cascade,
11156            } => {
11157                self.write_keyword("DROP COLUMN");
11158                if *if_exists {
11159                    self.write_space();
11160                    self.write_keyword("IF EXISTS");
11161                }
11162                self.write_space();
11163                self.generate_identifier(name)?;
11164                if *cascade {
11165                    self.write_space();
11166                    self.write_keyword("CASCADE");
11167                }
11168            }
11169            AlterTableAction::DropColumns { names } => {
11170                self.write_keyword("DROP COLUMNS");
11171                self.write(" (");
11172                for (i, name) in names.iter().enumerate() {
11173                    if i > 0 {
11174                        self.write(", ");
11175                    }
11176                    self.generate_identifier(name)?;
11177                }
11178                self.write(")");
11179            }
11180            AlterTableAction::RenameColumn {
11181                old_name,
11182                new_name,
11183                if_exists,
11184            } => {
11185                self.write_keyword("RENAME COLUMN");
11186                if *if_exists {
11187                    self.write_space();
11188                    self.write_keyword("IF EXISTS");
11189                }
11190                self.write_space();
11191                self.generate_identifier(old_name)?;
11192                self.write_space();
11193                self.write_keyword("TO");
11194                self.write_space();
11195                self.generate_identifier(new_name)?;
11196            }
11197            AlterTableAction::AlterColumn {
11198                name,
11199                action,
11200                use_modify_keyword,
11201            } => {
11202                use crate::dialects::DialectType;
11203                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
11204                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
11205                let use_modify = *use_modify_keyword
11206                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
11207                        && matches!(action, AlterColumnAction::SetDataType { .. }));
11208                if use_modify {
11209                    self.write_keyword("MODIFY COLUMN");
11210                    self.write_space();
11211                    self.generate_identifier(name)?;
11212                    // For MODIFY COLUMN, output the type directly
11213                    if let AlterColumnAction::SetDataType {
11214                        data_type,
11215                        using: _,
11216                        collate,
11217                    } = action
11218                    {
11219                        self.write_space();
11220                        self.generate_data_type(data_type)?;
11221                        // Output COLLATE clause if present
11222                        if let Some(collate_name) = collate {
11223                            self.write_space();
11224                            self.write_keyword("COLLATE");
11225                            self.write_space();
11226                            // Output as single-quoted string
11227                            self.write(&format!("'{}'", collate_name));
11228                        }
11229                    } else {
11230                        self.write_space();
11231                        self.generate_alter_column_action(action)?;
11232                    }
11233                } else if matches!(self.config.dialect, Some(DialectType::Hive))
11234                    && matches!(action, AlterColumnAction::SetDataType { .. })
11235                {
11236                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
11237                    self.write_keyword("CHANGE COLUMN");
11238                    self.write_space();
11239                    self.generate_identifier(name)?;
11240                    self.write_space();
11241                    self.generate_identifier(name)?;
11242                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
11243                        self.write_space();
11244                        self.generate_data_type(data_type)?;
11245                    }
11246                } else {
11247                    self.write_keyword("ALTER COLUMN");
11248                    self.write_space();
11249                    self.generate_identifier(name)?;
11250                    self.write_space();
11251                    self.generate_alter_column_action(action)?;
11252                }
11253            }
11254            AlterTableAction::RenameTable(new_name) => {
11255                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
11256                let mysql_like = matches!(
11257                    self.config.dialect,
11258                    Some(DialectType::MySQL)
11259                        | Some(DialectType::Doris)
11260                        | Some(DialectType::StarRocks)
11261                        | Some(DialectType::SingleStore)
11262                );
11263                if mysql_like {
11264                    self.write_keyword("RENAME");
11265                } else {
11266                    self.write_keyword("RENAME TO");
11267                }
11268                self.write_space();
11269                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
11270                let rename_table_with_db = !matches!(
11271                    self.config.dialect,
11272                    Some(DialectType::Doris)
11273                        | Some(DialectType::DuckDB)
11274                        | Some(DialectType::BigQuery)
11275                        | Some(DialectType::PostgreSQL)
11276                );
11277                if !rename_table_with_db {
11278                    let mut stripped = new_name.clone();
11279                    stripped.schema = None;
11280                    stripped.catalog = None;
11281                    self.generate_table(&stripped)?;
11282                } else {
11283                    self.generate_table(new_name)?;
11284                }
11285            }
11286            AlterTableAction::AddConstraint(constraint) => {
11287                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
11288                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
11289                if !is_continuation {
11290                    self.write_keyword("ADD");
11291                    self.write_space();
11292                }
11293                self.generate_table_constraint(constraint)?;
11294            }
11295            AlterTableAction::DropConstraint { name, if_exists } => {
11296                self.write_keyword("DROP CONSTRAINT");
11297                if *if_exists {
11298                    self.write_space();
11299                    self.write_keyword("IF EXISTS");
11300                }
11301                self.write_space();
11302                self.generate_identifier(name)?;
11303            }
11304            AlterTableAction::DropForeignKey { name } => {
11305                self.write_keyword("DROP FOREIGN KEY");
11306                self.write_space();
11307                self.generate_identifier(name)?;
11308            }
11309            AlterTableAction::DropPartition {
11310                partitions,
11311                if_exists,
11312            } => {
11313                self.write_keyword("DROP");
11314                if *if_exists {
11315                    self.write_space();
11316                    self.write_keyword("IF EXISTS");
11317                }
11318                for (i, partition) in partitions.iter().enumerate() {
11319                    if i > 0 {
11320                        self.write(",");
11321                    }
11322                    self.write_space();
11323                    self.write_keyword("PARTITION");
11324                    // Check for special ClickHouse partition formats
11325                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
11326                        // ClickHouse: PARTITION <expression>
11327                        self.write_space();
11328                        self.generate_expression(&partition[0].1)?;
11329                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
11330                        // ClickHouse: PARTITION ALL
11331                        self.write_space();
11332                        self.write_keyword("ALL");
11333                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
11334                        // ClickHouse: PARTITION ID 'string'
11335                        self.write_space();
11336                        self.write_keyword("ID");
11337                        self.write_space();
11338                        self.generate_expression(&partition[0].1)?;
11339                    } else {
11340                        // Standard SQL: PARTITION(key=value, ...)
11341                        self.write("(");
11342                        for (j, (key, value)) in partition.iter().enumerate() {
11343                            if j > 0 {
11344                                self.write(", ");
11345                            }
11346                            self.generate_identifier(key)?;
11347                            self.write(" = ");
11348                            self.generate_expression(value)?;
11349                        }
11350                        self.write(")");
11351                    }
11352                }
11353            }
11354            AlterTableAction::Delete { where_clause } => {
11355                self.write_keyword("DELETE");
11356                self.write_space();
11357                self.write_keyword("WHERE");
11358                self.write_space();
11359                self.generate_expression(where_clause)?;
11360            }
11361            AlterTableAction::SwapWith(target) => {
11362                self.write_keyword("SWAP WITH");
11363                self.write_space();
11364                self.generate_table(target)?;
11365            }
11366            AlterTableAction::SetProperty { properties } => {
11367                use crate::dialects::DialectType;
11368                self.write_keyword("SET");
11369                // Trino/Presto use SET PROPERTIES syntax with spaces around =
11370                let is_trino_presto = matches!(
11371                    self.config.dialect,
11372                    Some(DialectType::Trino) | Some(DialectType::Presto)
11373                );
11374                if is_trino_presto {
11375                    self.write_space();
11376                    self.write_keyword("PROPERTIES");
11377                }
11378                let eq = if is_trino_presto { " = " } else { "=" };
11379                for (i, (key, value)) in properties.iter().enumerate() {
11380                    if i > 0 {
11381                        self.write(",");
11382                    }
11383                    self.write_space();
11384                    // Handle quoted property names for Trino
11385                    if key.contains(' ') {
11386                        self.generate_string_literal(key)?;
11387                    } else {
11388                        self.write(key);
11389                    }
11390                    self.write(eq);
11391                    self.generate_expression(value)?;
11392                }
11393            }
11394            AlterTableAction::UnsetProperty { properties } => {
11395                self.write_keyword("UNSET");
11396                for (i, name) in properties.iter().enumerate() {
11397                    if i > 0 {
11398                        self.write(",");
11399                    }
11400                    self.write_space();
11401                    self.write(name);
11402                }
11403            }
11404            AlterTableAction::ClusterBy { expressions } => {
11405                self.write_keyword("CLUSTER BY");
11406                self.write(" (");
11407                for (i, expr) in expressions.iter().enumerate() {
11408                    if i > 0 {
11409                        self.write(", ");
11410                    }
11411                    self.generate_expression(expr)?;
11412                }
11413                self.write(")");
11414            }
11415            AlterTableAction::SetTag { expressions } => {
11416                self.write_keyword("SET TAG");
11417                for (i, (key, value)) in expressions.iter().enumerate() {
11418                    if i > 0 {
11419                        self.write(",");
11420                    }
11421                    self.write_space();
11422                    self.write(key);
11423                    self.write(" = ");
11424                    self.generate_expression(value)?;
11425                }
11426            }
11427            AlterTableAction::UnsetTag { names } => {
11428                self.write_keyword("UNSET TAG");
11429                for (i, name) in names.iter().enumerate() {
11430                    if i > 0 {
11431                        self.write(",");
11432                    }
11433                    self.write_space();
11434                    self.write(name);
11435                }
11436            }
11437            AlterTableAction::SetOptions { expressions } => {
11438                self.write_keyword("SET");
11439                self.write(" (");
11440                for (i, expr) in expressions.iter().enumerate() {
11441                    if i > 0 {
11442                        self.write(", ");
11443                    }
11444                    self.generate_expression(expr)?;
11445                }
11446                self.write(")");
11447            }
11448            AlterTableAction::AlterIndex { name, visible } => {
11449                self.write_keyword("ALTER INDEX");
11450                self.write_space();
11451                self.generate_identifier(name)?;
11452                self.write_space();
11453                if *visible {
11454                    self.write_keyword("VISIBLE");
11455                } else {
11456                    self.write_keyword("INVISIBLE");
11457                }
11458            }
11459            AlterTableAction::SetAttribute { attribute } => {
11460                self.write_keyword("SET");
11461                self.write_space();
11462                self.write_keyword(attribute);
11463            }
11464            AlterTableAction::SetStageFileFormat { options } => {
11465                self.write_keyword("SET");
11466                self.write_space();
11467                self.write_keyword("STAGE_FILE_FORMAT");
11468                self.write(" = (");
11469                if let Some(opts) = options {
11470                    self.generate_space_separated_properties(opts)?;
11471                }
11472                self.write(")");
11473            }
11474            AlterTableAction::SetStageCopyOptions { options } => {
11475                self.write_keyword("SET");
11476                self.write_space();
11477                self.write_keyword("STAGE_COPY_OPTIONS");
11478                self.write(" = (");
11479                if let Some(opts) = options {
11480                    self.generate_space_separated_properties(opts)?;
11481                }
11482                self.write(")");
11483            }
11484            AlterTableAction::AddColumns { columns, cascade } => {
11485                // Oracle uses ADD (...) without COLUMNS keyword
11486                // Hive/Spark uses ADD COLUMNS (...)
11487                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
11488                if is_oracle {
11489                    self.write_keyword("ADD");
11490                } else {
11491                    self.write_keyword("ADD COLUMNS");
11492                }
11493                self.write(" (");
11494                for (i, col) in columns.iter().enumerate() {
11495                    if i > 0 {
11496                        self.write(", ");
11497                    }
11498                    self.generate_column_def(col)?;
11499                }
11500                self.write(")");
11501                if *cascade {
11502                    self.write_space();
11503                    self.write_keyword("CASCADE");
11504                }
11505            }
11506            AlterTableAction::ChangeColumn {
11507                old_name,
11508                new_name,
11509                data_type,
11510                comment,
11511                cascade,
11512            } => {
11513                use crate::dialects::DialectType;
11514                let is_spark = matches!(
11515                    self.config.dialect,
11516                    Some(DialectType::Spark) | Some(DialectType::Databricks)
11517                );
11518                let is_rename = old_name.name != new_name.name;
11519
11520                if is_spark {
11521                    if is_rename {
11522                        // Spark: RENAME COLUMN old TO new
11523                        self.write_keyword("RENAME COLUMN");
11524                        self.write_space();
11525                        self.generate_identifier(old_name)?;
11526                        self.write_space();
11527                        self.write_keyword("TO");
11528                        self.write_space();
11529                        self.generate_identifier(new_name)?;
11530                    } else if comment.is_some() {
11531                        // Spark: ALTER COLUMN old COMMENT 'comment'
11532                        self.write_keyword("ALTER COLUMN");
11533                        self.write_space();
11534                        self.generate_identifier(old_name)?;
11535                        self.write_space();
11536                        self.write_keyword("COMMENT");
11537                        self.write_space();
11538                        self.write("'");
11539                        self.write(comment.as_ref().unwrap());
11540                        self.write("'");
11541                    } else if data_type.is_some() {
11542                        // Spark: ALTER COLUMN old TYPE data_type
11543                        self.write_keyword("ALTER COLUMN");
11544                        self.write_space();
11545                        self.generate_identifier(old_name)?;
11546                        self.write_space();
11547                        self.write_keyword("TYPE");
11548                        self.write_space();
11549                        self.generate_data_type(data_type.as_ref().unwrap())?;
11550                    } else {
11551                        // Fallback to CHANGE COLUMN
11552                        self.write_keyword("CHANGE COLUMN");
11553                        self.write_space();
11554                        self.generate_identifier(old_name)?;
11555                        self.write_space();
11556                        self.generate_identifier(new_name)?;
11557                    }
11558                } else {
11559                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
11560                    if data_type.is_some() {
11561                        self.write_keyword("CHANGE COLUMN");
11562                    } else {
11563                        self.write_keyword("CHANGE");
11564                    }
11565                    self.write_space();
11566                    self.generate_identifier(old_name)?;
11567                    self.write_space();
11568                    self.generate_identifier(new_name)?;
11569                    if let Some(ref dt) = data_type {
11570                        self.write_space();
11571                        self.generate_data_type(dt)?;
11572                    }
11573                    if let Some(ref c) = comment {
11574                        self.write_space();
11575                        self.write_keyword("COMMENT");
11576                        self.write_space();
11577                        self.write("'");
11578                        self.write(c);
11579                        self.write("'");
11580                    }
11581                    if *cascade {
11582                        self.write_space();
11583                        self.write_keyword("CASCADE");
11584                    }
11585                }
11586            }
11587            AlterTableAction::AddPartition {
11588                partition,
11589                if_not_exists,
11590                location,
11591            } => {
11592                self.write_keyword("ADD");
11593                self.write_space();
11594                if *if_not_exists {
11595                    self.write_keyword("IF NOT EXISTS");
11596                    self.write_space();
11597                }
11598                self.generate_expression(partition)?;
11599                if let Some(ref loc) = location {
11600                    self.write_space();
11601                    self.write_keyword("LOCATION");
11602                    self.write_space();
11603                    self.generate_expression(loc)?;
11604                }
11605            }
11606            AlterTableAction::AlterSortKey {
11607                this,
11608                expressions,
11609                compound,
11610            } => {
11611                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
11612                self.write_keyword("ALTER");
11613                if *compound {
11614                    self.write_space();
11615                    self.write_keyword("COMPOUND");
11616                }
11617                self.write_space();
11618                self.write_keyword("SORTKEY");
11619                self.write_space();
11620                if let Some(style) = this {
11621                    self.write_keyword(style);
11622                } else if !expressions.is_empty() {
11623                    self.write("(");
11624                    for (i, expr) in expressions.iter().enumerate() {
11625                        if i > 0 {
11626                            self.write(", ");
11627                        }
11628                        self.generate_expression(expr)?;
11629                    }
11630                    self.write(")");
11631                }
11632            }
11633            AlterTableAction::AlterDistStyle { style, distkey } => {
11634                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
11635                self.write_keyword("ALTER");
11636                self.write_space();
11637                self.write_keyword("DISTSTYLE");
11638                self.write_space();
11639                self.write_keyword(style);
11640                if let Some(col) = distkey {
11641                    self.write_space();
11642                    self.write_keyword("DISTKEY");
11643                    self.write_space();
11644                    self.generate_identifier(col)?;
11645                }
11646            }
11647            AlterTableAction::SetTableProperties { properties } => {
11648                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
11649                self.write_keyword("SET TABLE PROPERTIES");
11650                self.write(" (");
11651                for (i, (key, value)) in properties.iter().enumerate() {
11652                    if i > 0 {
11653                        self.write(", ");
11654                    }
11655                    self.generate_expression(key)?;
11656                    self.write(" = ");
11657                    self.generate_expression(value)?;
11658                }
11659                self.write(")");
11660            }
11661            AlterTableAction::SetLocation { location } => {
11662                // Redshift: SET LOCATION 's3://bucket/folder/'
11663                self.write_keyword("SET LOCATION");
11664                self.write_space();
11665                self.write("'");
11666                self.write(location);
11667                self.write("'");
11668            }
11669            AlterTableAction::SetFileFormat { format } => {
11670                // Redshift: SET FILE FORMAT AVRO
11671                self.write_keyword("SET FILE FORMAT");
11672                self.write_space();
11673                self.write_keyword(format);
11674            }
11675            AlterTableAction::ReplacePartition { partition, source } => {
11676                // ClickHouse: REPLACE PARTITION expr FROM source
11677                self.write_keyword("REPLACE PARTITION");
11678                self.write_space();
11679                self.generate_expression(partition)?;
11680                if let Some(src) = source {
11681                    self.write_space();
11682                    self.write_keyword("FROM");
11683                    self.write_space();
11684                    self.generate_expression(src)?;
11685                }
11686            }
11687            AlterTableAction::Raw { sql } => {
11688                self.write(sql);
11689            }
11690        }
11691        Ok(())
11692    }
11693
11694    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
11695        match action {
11696            AlterColumnAction::SetDataType {
11697                data_type,
11698                using,
11699                collate,
11700            } => {
11701                use crate::dialects::DialectType;
11702                // Dialect-specific type change syntax:
11703                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
11704                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
11705                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
11706                let is_no_prefix = matches!(
11707                    self.config.dialect,
11708                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
11709                );
11710                let is_type_only = matches!(
11711                    self.config.dialect,
11712                    Some(DialectType::Redshift)
11713                        | Some(DialectType::Spark)
11714                        | Some(DialectType::Databricks)
11715                );
11716                if is_type_only {
11717                    self.write_keyword("TYPE");
11718                    self.write_space();
11719                } else if !is_no_prefix {
11720                    self.write_keyword("SET DATA TYPE");
11721                    self.write_space();
11722                }
11723                self.generate_data_type(data_type)?;
11724                if let Some(ref collation) = collate {
11725                    self.write_space();
11726                    self.write_keyword("COLLATE");
11727                    self.write_space();
11728                    self.write(collation);
11729                }
11730                if let Some(ref using_expr) = using {
11731                    self.write_space();
11732                    self.write_keyword("USING");
11733                    self.write_space();
11734                    self.generate_expression(using_expr)?;
11735                }
11736            }
11737            AlterColumnAction::SetDefault(expr) => {
11738                self.write_keyword("SET DEFAULT");
11739                self.write_space();
11740                self.generate_expression(expr)?;
11741            }
11742            AlterColumnAction::DropDefault => {
11743                self.write_keyword("DROP DEFAULT");
11744            }
11745            AlterColumnAction::SetNotNull => {
11746                self.write_keyword("SET NOT NULL");
11747            }
11748            AlterColumnAction::DropNotNull => {
11749                self.write_keyword("DROP NOT NULL");
11750            }
11751            AlterColumnAction::Comment(comment) => {
11752                self.write_keyword("COMMENT");
11753                self.write_space();
11754                self.generate_string_literal(comment)?;
11755            }
11756            AlterColumnAction::SetVisible => {
11757                self.write_keyword("SET VISIBLE");
11758            }
11759            AlterColumnAction::SetInvisible => {
11760                self.write_keyword("SET INVISIBLE");
11761            }
11762        }
11763        Ok(())
11764    }
11765
11766    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
11767        self.write_keyword("CREATE");
11768
11769        if ci.unique {
11770            self.write_space();
11771            self.write_keyword("UNIQUE");
11772        }
11773
11774        // TSQL CLUSTERED/NONCLUSTERED modifier
11775        if let Some(ref clustered) = ci.clustered {
11776            self.write_space();
11777            self.write_keyword(clustered);
11778        }
11779
11780        self.write_space();
11781        self.write_keyword("INDEX");
11782
11783        // PostgreSQL CONCURRENTLY modifier
11784        if ci.concurrently {
11785            self.write_space();
11786            self.write_keyword("CONCURRENTLY");
11787        }
11788
11789        if ci.if_not_exists {
11790            self.write_space();
11791            self.write_keyword("IF NOT EXISTS");
11792        }
11793
11794        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
11795        if !ci.name.name.is_empty() {
11796            self.write_space();
11797            self.generate_identifier(&ci.name)?;
11798        }
11799        self.write_space();
11800        self.write_keyword("ON");
11801        // Hive uses ON TABLE
11802        if matches!(self.config.dialect, Some(DialectType::Hive)) {
11803            self.write_space();
11804            self.write_keyword("TABLE");
11805        }
11806        self.write_space();
11807        self.generate_table(&ci.table)?;
11808
11809        // Column list (optional for COLUMNSTORE indexes)
11810        // Standard SQL convention: ON t(a) without space before paren
11811        if !ci.columns.is_empty() || ci.using.is_some() {
11812            let space_before_paren = false;
11813
11814            if let Some(ref using) = ci.using {
11815                self.write_space();
11816                self.write_keyword("USING");
11817                self.write_space();
11818                self.write(using);
11819                if space_before_paren {
11820                    self.write(" (");
11821                } else {
11822                    self.write("(");
11823                }
11824            } else {
11825                if space_before_paren {
11826                    self.write(" (");
11827                } else {
11828                    self.write("(");
11829                }
11830            }
11831            for (i, col) in ci.columns.iter().enumerate() {
11832                if i > 0 {
11833                    self.write(", ");
11834                }
11835                self.generate_identifier(&col.column)?;
11836                if let Some(ref opclass) = col.opclass {
11837                    self.write_space();
11838                    self.write(opclass);
11839                }
11840                if col.desc {
11841                    self.write_space();
11842                    self.write_keyword("DESC");
11843                } else if col.asc {
11844                    self.write_space();
11845                    self.write_keyword("ASC");
11846                }
11847                if let Some(nulls_first) = col.nulls_first {
11848                    self.write_space();
11849                    self.write_keyword("NULLS");
11850                    self.write_space();
11851                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
11852                }
11853            }
11854            self.write(")");
11855        }
11856
11857        // PostgreSQL INCLUDE (col1, col2) clause
11858        if !ci.include_columns.is_empty() {
11859            self.write_space();
11860            self.write_keyword("INCLUDE");
11861            self.write(" (");
11862            for (i, col) in ci.include_columns.iter().enumerate() {
11863                if i > 0 {
11864                    self.write(", ");
11865                }
11866                self.generate_identifier(col)?;
11867            }
11868            self.write(")");
11869        }
11870
11871        // TSQL: WITH (option=value, ...) clause
11872        if !ci.with_options.is_empty() {
11873            self.write_space();
11874            self.write_keyword("WITH");
11875            self.write(" (");
11876            for (i, (key, value)) in ci.with_options.iter().enumerate() {
11877                if i > 0 {
11878                    self.write(", ");
11879                }
11880                self.write(key);
11881                self.write("=");
11882                self.write(value);
11883            }
11884            self.write(")");
11885        }
11886
11887        // PostgreSQL WHERE clause for partial indexes
11888        if let Some(ref where_clause) = ci.where_clause {
11889            self.write_space();
11890            self.write_keyword("WHERE");
11891            self.write_space();
11892            self.generate_expression(where_clause)?;
11893        }
11894
11895        // TSQL: ON filegroup or partition scheme clause
11896        if let Some(ref on_fg) = ci.on_filegroup {
11897            self.write_space();
11898            self.write_keyword("ON");
11899            self.write_space();
11900            self.write(on_fg);
11901        }
11902
11903        Ok(())
11904    }
11905
11906    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
11907        self.write_keyword("DROP INDEX");
11908
11909        if di.concurrently {
11910            self.write_space();
11911            self.write_keyword("CONCURRENTLY");
11912        }
11913
11914        if di.if_exists {
11915            self.write_space();
11916            self.write_keyword("IF EXISTS");
11917        }
11918
11919        self.write_space();
11920        self.generate_table(&di.name)?;
11921
11922        if let Some(ref table) = di.table {
11923            self.write_space();
11924            self.write_keyword("ON");
11925            self.write_space();
11926            self.generate_table(table)?;
11927        }
11928
11929        Ok(())
11930    }
11931
11932    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
11933        self.write_keyword("CREATE");
11934
11935        // MySQL: ALGORITHM=...
11936        if let Some(ref algorithm) = cv.algorithm {
11937            self.write_space();
11938            self.write_keyword("ALGORITHM");
11939            self.write("=");
11940            self.write_keyword(algorithm);
11941        }
11942
11943        // MySQL: DEFINER=...
11944        if let Some(ref definer) = cv.definer {
11945            self.write_space();
11946            self.write_keyword("DEFINER");
11947            self.write("=");
11948            self.write(definer);
11949        }
11950
11951        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
11952        if cv.security_sql_style && !cv.security_after_name {
11953            if let Some(ref security) = cv.security {
11954                self.write_space();
11955                self.write_keyword("SQL SECURITY");
11956                self.write_space();
11957                match security {
11958                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11959                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11960                    FunctionSecurity::None => self.write_keyword("NONE"),
11961                }
11962            }
11963        }
11964
11965        if cv.or_alter {
11966            self.write_space();
11967            self.write_keyword("OR ALTER");
11968        } else if cv.or_replace {
11969            self.write_space();
11970            self.write_keyword("OR REPLACE");
11971        }
11972
11973        if cv.temporary {
11974            self.write_space();
11975            self.write_keyword("TEMPORARY");
11976        }
11977
11978        if cv.materialized {
11979            self.write_space();
11980            self.write_keyword("MATERIALIZED");
11981        }
11982
11983        // Snowflake: SECURE VIEW
11984        if cv.secure {
11985            self.write_space();
11986            self.write_keyword("SECURE");
11987        }
11988
11989        self.write_space();
11990        self.write_keyword("VIEW");
11991
11992        if cv.if_not_exists {
11993            self.write_space();
11994            self.write_keyword("IF NOT EXISTS");
11995        }
11996
11997        self.write_space();
11998        self.generate_table(&cv.name)?;
11999
12000        // ClickHouse: ON CLUSTER clause
12001        if let Some(ref on_cluster) = cv.on_cluster {
12002            self.write_space();
12003            self.generate_on_cluster(on_cluster)?;
12004        }
12005
12006        // ClickHouse: TO destination_table
12007        if let Some(ref to_table) = cv.to_table {
12008            self.write_space();
12009            self.write_keyword("TO");
12010            self.write_space();
12011            self.generate_table(to_table)?;
12012        }
12013
12014        // For regular VIEW: columns come before COPY GRANTS
12015        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
12016        if !cv.materialized {
12017            // Regular VIEW: columns first
12018            if let Some(ref schema) = cv.schema {
12019                self.write(" (");
12020                for (i, expr) in schema.expressions.iter().enumerate() {
12021                    if i > 0 {
12022                        self.write(", ");
12023                    }
12024                    self.generate_expression(expr)?;
12025                }
12026                self.write(")");
12027            } else if !cv.columns.is_empty() {
12028                self.write(" (");
12029                for (i, col) in cv.columns.iter().enumerate() {
12030                    if i > 0 {
12031                        self.write(", ");
12032                    }
12033                    self.generate_identifier(&col.name)?;
12034                    // BigQuery: OPTIONS (key=value, ...) on view column
12035                    if !col.options.is_empty() {
12036                        self.write_space();
12037                        self.generate_options_clause(&col.options)?;
12038                    }
12039                    if let Some(ref comment) = col.comment {
12040                        self.write_space();
12041                        self.write_keyword("COMMENT");
12042                        self.write_space();
12043                        self.generate_string_literal(comment)?;
12044                    }
12045                }
12046                self.write(")");
12047            }
12048
12049            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
12050            // Also handles SQL SECURITY after view name (security_after_name)
12051            if !cv.security_sql_style || cv.security_after_name {
12052                if let Some(ref security) = cv.security {
12053                    self.write_space();
12054                    if cv.security_sql_style {
12055                        self.write_keyword("SQL SECURITY");
12056                    } else {
12057                        self.write_keyword("SECURITY");
12058                    }
12059                    self.write_space();
12060                    match security {
12061                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
12062                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
12063                        FunctionSecurity::None => self.write_keyword("NONE"),
12064                    }
12065                }
12066            }
12067
12068            // Snowflake: COPY GRANTS
12069            if cv.copy_grants {
12070                self.write_space();
12071                self.write_keyword("COPY GRANTS");
12072            }
12073        } else {
12074            // MATERIALIZED VIEW: COPY GRANTS first
12075            if cv.copy_grants {
12076                self.write_space();
12077                self.write_keyword("COPY GRANTS");
12078            }
12079
12080            // Doris: If we have a schema (typed columns), generate that instead
12081            if let Some(ref schema) = cv.schema {
12082                self.write(" (");
12083                for (i, expr) in schema.expressions.iter().enumerate() {
12084                    if i > 0 {
12085                        self.write(", ");
12086                    }
12087                    self.generate_expression(expr)?;
12088                }
12089                self.write(")");
12090            } else if !cv.columns.is_empty() {
12091                // Then columns (simple column names without types)
12092                self.write(" (");
12093                for (i, col) in cv.columns.iter().enumerate() {
12094                    if i > 0 {
12095                        self.write(", ");
12096                    }
12097                    self.generate_identifier(&col.name)?;
12098                    // BigQuery: OPTIONS (key=value, ...) on view column
12099                    if !col.options.is_empty() {
12100                        self.write_space();
12101                        self.generate_options_clause(&col.options)?;
12102                    }
12103                    if let Some(ref comment) = col.comment {
12104                        self.write_space();
12105                        self.write_keyword("COMMENT");
12106                        self.write_space();
12107                        self.generate_string_literal(comment)?;
12108                    }
12109                }
12110                self.write(")");
12111            }
12112
12113            // Doris: KEY (columns) for materialized views
12114            if let Some(ref unique_key) = cv.unique_key {
12115                self.write_space();
12116                self.write_keyword("KEY");
12117                self.write(" (");
12118                for (i, expr) in unique_key.expressions.iter().enumerate() {
12119                    if i > 0 {
12120                        self.write(", ");
12121                    }
12122                    self.generate_expression(expr)?;
12123                }
12124                self.write(")");
12125            }
12126        }
12127
12128        if let Some(ref row_access_policy) = cv.row_access_policy {
12129            self.write_space();
12130            self.write_keyword("WITH");
12131            self.write_space();
12132            self.write(row_access_policy);
12133        }
12134
12135        // Snowflake: COMMENT = 'text'
12136        if let Some(ref comment) = cv.comment {
12137            self.write_space();
12138            self.write_keyword("COMMENT");
12139            self.write("=");
12140            self.generate_string_literal(comment)?;
12141        }
12142
12143        // Snowflake: TAG (name='value', ...)
12144        if !cv.tags.is_empty() {
12145            self.write_space();
12146            self.write_keyword("TAG");
12147            self.write(" (");
12148            for (i, (name, value)) in cv.tags.iter().enumerate() {
12149                if i > 0 {
12150                    self.write(", ");
12151                }
12152                self.write(name);
12153                self.write("='");
12154                self.write(value);
12155                self.write("'");
12156            }
12157            self.write(")");
12158        }
12159
12160        // BigQuery: OPTIONS (key=value, ...)
12161        if !cv.options.is_empty() {
12162            self.write_space();
12163            self.generate_options_clause(&cv.options)?;
12164        }
12165
12166        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
12167        if let Some(ref build) = cv.build {
12168            self.write_space();
12169            self.write_keyword("BUILD");
12170            self.write_space();
12171            self.write_keyword(build);
12172        }
12173
12174        // Doris: REFRESH clause for materialized views
12175        if let Some(ref refresh) = cv.refresh {
12176            self.write_space();
12177            self.generate_refresh_trigger_property(refresh)?;
12178        }
12179
12180        // Redshift: AUTO REFRESH YES|NO for materialized views
12181        if let Some(auto_refresh) = cv.auto_refresh {
12182            self.write_space();
12183            self.write_keyword("AUTO REFRESH");
12184            self.write_space();
12185            if auto_refresh {
12186                self.write_keyword("YES");
12187            } else {
12188                self.write_keyword("NO");
12189            }
12190        }
12191
12192        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
12193        for prop in &cv.table_properties {
12194            self.write_space();
12195            self.generate_expression(prop)?;
12196        }
12197
12198        // ClickHouse: POPULATE / EMPTY before AS
12199        if let Some(ref population) = cv.clickhouse_population {
12200            self.write_space();
12201            self.write_keyword(population);
12202        }
12203
12204        // Only output AS clause if there's a real query (not just NULL placeholder)
12205        if !matches!(&cv.query, Expression::Null(_)) {
12206            self.write_space();
12207            self.write_keyword("AS");
12208            self.write_space();
12209
12210            // Teradata: LOCKING clause (between AS and query)
12211            if let Some(ref mode) = cv.locking_mode {
12212                self.write_keyword("LOCKING");
12213                self.write_space();
12214                self.write_keyword(mode);
12215                if let Some(ref access) = cv.locking_access {
12216                    self.write_space();
12217                    self.write_keyword("FOR");
12218                    self.write_space();
12219                    self.write_keyword(access);
12220                }
12221                self.write_space();
12222            }
12223
12224            if cv.query_parenthesized {
12225                self.write("(");
12226            }
12227            self.generate_expression(&cv.query)?;
12228            if cv.query_parenthesized {
12229                self.write(")");
12230            }
12231        }
12232
12233        // Redshift: WITH NO SCHEMA BINDING (after query)
12234        if cv.no_schema_binding {
12235            self.write_space();
12236            self.write_keyword("WITH NO SCHEMA BINDING");
12237        }
12238
12239        Ok(())
12240    }
12241
12242    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
12243        self.write_keyword("DROP");
12244
12245        if dv.materialized {
12246            self.write_space();
12247            self.write_keyword("MATERIALIZED");
12248        }
12249
12250        self.write_space();
12251        self.write_keyword("VIEW");
12252
12253        if dv.if_exists {
12254            self.write_space();
12255            self.write_keyword("IF EXISTS");
12256        }
12257
12258        self.write_space();
12259        self.generate_table(&dv.name)?;
12260
12261        Ok(())
12262    }
12263
12264    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
12265        match tr.target {
12266            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
12267            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
12268        }
12269        if tr.if_exists {
12270            self.write_space();
12271            self.write_keyword("IF EXISTS");
12272        }
12273        self.write_space();
12274        self.generate_table(&tr.table)?;
12275
12276        // ClickHouse: ON CLUSTER clause
12277        if let Some(ref on_cluster) = tr.on_cluster {
12278            self.write_space();
12279            self.generate_on_cluster(on_cluster)?;
12280        }
12281
12282        // Check if first table has a * (multi-table with star)
12283        if !tr.extra_tables.is_empty() {
12284            // Check if the first entry matches the main table (star case)
12285            let skip_first = if let Some(first) = tr.extra_tables.first() {
12286                first.table.name == tr.table.name && first.star
12287            } else {
12288                false
12289            };
12290
12291            // PostgreSQL normalizes away the * suffix (it's the default behavior)
12292            let strip_star = matches!(
12293                self.config.dialect,
12294                Some(crate::dialects::DialectType::PostgreSQL)
12295                    | Some(crate::dialects::DialectType::Redshift)
12296            );
12297            if skip_first && !strip_star {
12298                self.write("*");
12299            }
12300
12301            // Generate additional tables
12302            for (i, entry) in tr.extra_tables.iter().enumerate() {
12303                if i == 0 && skip_first {
12304                    continue; // Already handled the star for first table
12305                }
12306                self.write(", ");
12307                self.generate_table(&entry.table)?;
12308                if entry.star && !strip_star {
12309                    self.write("*");
12310                }
12311            }
12312        }
12313
12314        // RESTART/CONTINUE IDENTITY
12315        if let Some(identity) = &tr.identity {
12316            self.write_space();
12317            match identity {
12318                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
12319                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
12320            }
12321        }
12322
12323        if tr.cascade {
12324            self.write_space();
12325            self.write_keyword("CASCADE");
12326        }
12327
12328        if tr.restrict {
12329            self.write_space();
12330            self.write_keyword("RESTRICT");
12331        }
12332
12333        // Output Hive PARTITION clause
12334        if let Some(ref partition) = tr.partition {
12335            self.write_space();
12336            self.generate_expression(partition)?;
12337        }
12338
12339        Ok(())
12340    }
12341
12342    fn generate_use(&mut self, u: &Use) -> Result<()> {
12343        // Teradata uses "DATABASE <name>" instead of "USE <name>"
12344        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
12345            self.write_keyword("DATABASE");
12346            self.write_space();
12347            self.generate_identifier(&u.this)?;
12348            return Ok(());
12349        }
12350
12351        self.write_keyword("USE");
12352
12353        if let Some(kind) = &u.kind {
12354            self.write_space();
12355            match kind {
12356                UseKind::Database => self.write_keyword("DATABASE"),
12357                UseKind::Schema => self.write_keyword("SCHEMA"),
12358                UseKind::Role => self.write_keyword("ROLE"),
12359                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
12360                UseKind::Catalog => self.write_keyword("CATALOG"),
12361                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
12362            }
12363        }
12364
12365        self.write_space();
12366        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
12367        // without quoting, since these are keywords not identifiers
12368        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
12369            self.write(&u.this.name);
12370        } else {
12371            self.generate_identifier(&u.this)?;
12372        }
12373        Ok(())
12374    }
12375
12376    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
12377        self.write_keyword("CACHE");
12378        if c.lazy {
12379            self.write_space();
12380            self.write_keyword("LAZY");
12381        }
12382        self.write_space();
12383        self.write_keyword("TABLE");
12384        self.write_space();
12385        self.generate_identifier(&c.table)?;
12386
12387        // OPTIONS clause
12388        if !c.options.is_empty() {
12389            self.write_space();
12390            self.write_keyword("OPTIONS");
12391            self.write("(");
12392            for (i, (key, value)) in c.options.iter().enumerate() {
12393                if i > 0 {
12394                    self.write(", ");
12395                }
12396                self.generate_expression(key)?;
12397                self.write(" = ");
12398                self.generate_expression(value)?;
12399            }
12400            self.write(")");
12401        }
12402
12403        // AS query
12404        if let Some(query) = &c.query {
12405            self.write_space();
12406            self.write_keyword("AS");
12407            self.write_space();
12408            self.generate_expression(query)?;
12409        }
12410
12411        Ok(())
12412    }
12413
12414    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
12415        self.write_keyword("UNCACHE TABLE");
12416        if u.if_exists {
12417            self.write_space();
12418            self.write_keyword("IF EXISTS");
12419        }
12420        self.write_space();
12421        self.generate_identifier(&u.table)?;
12422        Ok(())
12423    }
12424
12425    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
12426        self.write_keyword("LOAD DATA");
12427        if l.local {
12428            self.write_space();
12429            self.write_keyword("LOCAL");
12430        }
12431        self.write_space();
12432        self.write_keyword("INPATH");
12433        self.write_space();
12434        self.write("'");
12435        self.write(&l.inpath);
12436        self.write("'");
12437
12438        if l.overwrite {
12439            self.write_space();
12440            self.write_keyword("OVERWRITE");
12441        }
12442
12443        self.write_space();
12444        self.write_keyword("INTO TABLE");
12445        self.write_space();
12446        self.generate_expression(&l.table)?;
12447
12448        // PARTITION clause
12449        if !l.partition.is_empty() {
12450            self.write_space();
12451            self.write_keyword("PARTITION");
12452            self.write("(");
12453            for (i, (col, val)) in l.partition.iter().enumerate() {
12454                if i > 0 {
12455                    self.write(", ");
12456                }
12457                self.generate_identifier(col)?;
12458                self.write(" = ");
12459                self.generate_expression(val)?;
12460            }
12461            self.write(")");
12462        }
12463
12464        // INPUTFORMAT clause
12465        if let Some(fmt) = &l.input_format {
12466            self.write_space();
12467            self.write_keyword("INPUTFORMAT");
12468            self.write_space();
12469            self.write("'");
12470            self.write(fmt);
12471            self.write("'");
12472        }
12473
12474        // SERDE clause
12475        if let Some(serde) = &l.serde {
12476            self.write_space();
12477            self.write_keyword("SERDE");
12478            self.write_space();
12479            self.write("'");
12480            self.write(serde);
12481            self.write("'");
12482        }
12483
12484        Ok(())
12485    }
12486
12487    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
12488        self.write_keyword("PRAGMA");
12489        self.write_space();
12490
12491        // Schema prefix if present
12492        if let Some(schema) = &p.schema {
12493            self.generate_identifier(schema)?;
12494            self.write(".");
12495        }
12496
12497        // Pragma name
12498        self.generate_identifier(&p.name)?;
12499
12500        // Value assignment or function call
12501        if p.use_assignment_syntax {
12502            self.write(" = ");
12503            if let Some(value) = &p.value {
12504                self.generate_expression(value)?;
12505            } else if let Some(arg) = p.args.first() {
12506                self.generate_expression(arg)?;
12507            }
12508        } else if !p.args.is_empty() {
12509            self.write("(");
12510            for (i, arg) in p.args.iter().enumerate() {
12511                if i > 0 {
12512                    self.write(", ");
12513                }
12514                self.generate_expression(arg)?;
12515            }
12516            self.write(")");
12517        }
12518
12519        Ok(())
12520    }
12521
12522    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
12523        self.write_keyword("GRANT");
12524        self.write_space();
12525
12526        // Privileges (with optional column lists)
12527        for (i, privilege) in g.privileges.iter().enumerate() {
12528            if i > 0 {
12529                self.write(", ");
12530            }
12531            self.write_keyword(&privilege.name);
12532            // Output column list if present: SELECT(col1, col2)
12533            if !privilege.columns.is_empty() {
12534                self.write("(");
12535                for (j, col) in privilege.columns.iter().enumerate() {
12536                    if j > 0 {
12537                        self.write(", ");
12538                    }
12539                    self.write(col);
12540                }
12541                self.write(")");
12542            }
12543        }
12544
12545        self.write_space();
12546        self.write_keyword("ON");
12547        self.write_space();
12548
12549        // Object kind (TABLE, SCHEMA, etc.)
12550        if let Some(kind) = &g.kind {
12551            self.write_keyword(kind);
12552            self.write_space();
12553        }
12554
12555        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
12556        {
12557            use crate::dialects::DialectType;
12558            let should_upper = matches!(
12559                self.config.dialect,
12560                Some(DialectType::PostgreSQL)
12561                    | Some(DialectType::CockroachDB)
12562                    | Some(DialectType::Materialize)
12563                    | Some(DialectType::RisingWave)
12564            ) && (g.kind.as_deref() == Some("FUNCTION")
12565                || g.kind.as_deref() == Some("PROCEDURE"));
12566            if should_upper {
12567                use crate::expressions::Identifier;
12568                let upper_id = Identifier {
12569                    name: g.securable.name.to_ascii_uppercase(),
12570                    quoted: g.securable.quoted,
12571                    ..g.securable.clone()
12572                };
12573                self.generate_identifier(&upper_id)?;
12574            } else {
12575                self.generate_identifier(&g.securable)?;
12576            }
12577        }
12578
12579        // Function parameter types (if present)
12580        if !g.function_params.is_empty() {
12581            self.write("(");
12582            for (i, param) in g.function_params.iter().enumerate() {
12583                if i > 0 {
12584                    self.write(", ");
12585                }
12586                self.write(param);
12587            }
12588            self.write(")");
12589        }
12590
12591        self.write_space();
12592        self.write_keyword("TO");
12593        self.write_space();
12594
12595        // Principals
12596        for (i, principal) in g.principals.iter().enumerate() {
12597            if i > 0 {
12598                self.write(", ");
12599            }
12600            if principal.is_role {
12601                self.write_keyword("ROLE");
12602                self.write_space();
12603            } else if principal.is_group {
12604                self.write_keyword("GROUP");
12605                self.write_space();
12606            } else if principal.is_share {
12607                self.write_keyword("SHARE");
12608                self.write_space();
12609            }
12610            self.generate_identifier(&principal.name)?;
12611        }
12612
12613        // WITH GRANT OPTION
12614        if g.grant_option {
12615            self.write_space();
12616            self.write_keyword("WITH GRANT OPTION");
12617        }
12618
12619        // TSQL: AS principal
12620        if let Some(ref principal) = g.as_principal {
12621            self.write_space();
12622            self.write_keyword("AS");
12623            self.write_space();
12624            self.generate_identifier(principal)?;
12625        }
12626
12627        Ok(())
12628    }
12629
12630    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
12631        self.write_keyword("REVOKE");
12632        self.write_space();
12633
12634        // GRANT OPTION FOR
12635        if r.grant_option {
12636            self.write_keyword("GRANT OPTION FOR");
12637            self.write_space();
12638        }
12639
12640        // Privileges (with optional column lists)
12641        for (i, privilege) in r.privileges.iter().enumerate() {
12642            if i > 0 {
12643                self.write(", ");
12644            }
12645            self.write_keyword(&privilege.name);
12646            // Output column list if present: SELECT(col1, col2)
12647            if !privilege.columns.is_empty() {
12648                self.write("(");
12649                for (j, col) in privilege.columns.iter().enumerate() {
12650                    if j > 0 {
12651                        self.write(", ");
12652                    }
12653                    self.write(col);
12654                }
12655                self.write(")");
12656            }
12657        }
12658
12659        self.write_space();
12660        self.write_keyword("ON");
12661        self.write_space();
12662
12663        // Object kind
12664        if let Some(kind) = &r.kind {
12665            self.write_keyword(kind);
12666            self.write_space();
12667        }
12668
12669        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
12670        {
12671            use crate::dialects::DialectType;
12672            let should_upper = matches!(
12673                self.config.dialect,
12674                Some(DialectType::PostgreSQL)
12675                    | Some(DialectType::CockroachDB)
12676                    | Some(DialectType::Materialize)
12677                    | Some(DialectType::RisingWave)
12678            ) && (r.kind.as_deref() == Some("FUNCTION")
12679                || r.kind.as_deref() == Some("PROCEDURE"));
12680            if should_upper {
12681                use crate::expressions::Identifier;
12682                let upper_id = Identifier {
12683                    name: r.securable.name.to_ascii_uppercase(),
12684                    quoted: r.securable.quoted,
12685                    ..r.securable.clone()
12686                };
12687                self.generate_identifier(&upper_id)?;
12688            } else {
12689                self.generate_identifier(&r.securable)?;
12690            }
12691        }
12692
12693        // Function parameter types (if present)
12694        if !r.function_params.is_empty() {
12695            self.write("(");
12696            for (i, param) in r.function_params.iter().enumerate() {
12697                if i > 0 {
12698                    self.write(", ");
12699                }
12700                self.write(param);
12701            }
12702            self.write(")");
12703        }
12704
12705        self.write_space();
12706        self.write_keyword("FROM");
12707        self.write_space();
12708
12709        // Principals
12710        for (i, principal) in r.principals.iter().enumerate() {
12711            if i > 0 {
12712                self.write(", ");
12713            }
12714            if principal.is_role {
12715                self.write_keyword("ROLE");
12716                self.write_space();
12717            } else if principal.is_group {
12718                self.write_keyword("GROUP");
12719                self.write_space();
12720            } else if principal.is_share {
12721                self.write_keyword("SHARE");
12722                self.write_space();
12723            }
12724            self.generate_identifier(&principal.name)?;
12725        }
12726
12727        // CASCADE or RESTRICT
12728        if r.cascade {
12729            self.write_space();
12730            self.write_keyword("CASCADE");
12731        } else if r.restrict {
12732            self.write_space();
12733            self.write_keyword("RESTRICT");
12734        }
12735
12736        Ok(())
12737    }
12738
12739    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
12740        self.write_keyword("COMMENT");
12741
12742        // IF EXISTS
12743        if c.exists {
12744            self.write_space();
12745            self.write_keyword("IF EXISTS");
12746        }
12747
12748        self.write_space();
12749        self.write_keyword("ON");
12750
12751        // MATERIALIZED
12752        if c.materialized {
12753            self.write_space();
12754            self.write_keyword("MATERIALIZED");
12755        }
12756
12757        self.write_space();
12758        self.write_keyword(&c.kind);
12759        self.write_space();
12760
12761        // Object name
12762        self.generate_expression(&c.this)?;
12763
12764        self.write_space();
12765        self.write_keyword("IS");
12766        self.write_space();
12767
12768        // Comment expression
12769        self.generate_expression(&c.expression)?;
12770
12771        Ok(())
12772    }
12773
12774    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
12775        self.write_keyword("SET");
12776
12777        for (i, item) in s.items.iter().enumerate() {
12778            if i > 0 {
12779                self.write(",");
12780            }
12781            self.write_space();
12782
12783            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
12784            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
12785            if let Some(ref kind) = item.kind {
12786                // For VARIABLE kind, only output the keyword for dialects that require it
12787                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
12788                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
12789                if has_variable_kind {
12790                    if matches!(
12791                        self.config.dialect,
12792                        Some(DialectType::Spark | DialectType::Databricks | DialectType::DuckDB)
12793                    ) {
12794                        self.write_keyword("VARIABLE");
12795                        self.write_space();
12796                    }
12797                } else {
12798                    self.write_keyword(kind);
12799                    self.write_space();
12800                }
12801            }
12802
12803            // Check for special SET forms by name
12804            let name_str = match &item.name {
12805                Expression::Identifier(id) => Some(id.name.as_str()),
12806                _ => None,
12807            };
12808
12809            let is_transaction = name_str == Some("TRANSACTION");
12810            let is_character_set = name_str == Some("CHARACTER SET");
12811            let is_names = name_str == Some("NAMES");
12812            let is_collate = name_str == Some("COLLATE");
12813            let is_identity_insert = name_str == Some("IDENTITY_INSERT");
12814            let is_value_only =
12815                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
12816
12817            if is_transaction {
12818                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
12819                self.write_keyword("TRANSACTION");
12820                if let Expression::Identifier(id) = &item.value {
12821                    if !id.name.is_empty() {
12822                        self.write_space();
12823                        self.write(&id.name);
12824                    }
12825                }
12826            } else if is_character_set {
12827                // Output: SET CHARACTER SET <charset>
12828                self.write_keyword("CHARACTER SET");
12829                self.write_space();
12830                self.generate_set_value(&item.value)?;
12831            } else if is_names {
12832                // Output: SET NAMES <charset>
12833                self.write_keyword("NAMES");
12834                self.write_space();
12835                self.generate_set_value(&item.value)?;
12836            } else if is_collate {
12837                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
12838                self.write_keyword("COLLATE");
12839                self.write_space();
12840                self.generate_set_value(&item.value)?;
12841            } else if is_identity_insert {
12842                // T-SQL: SET IDENTITY_INSERT <table> ON|OFF
12843                self.write_keyword("IDENTITY_INSERT");
12844                self.write_space();
12845                self.generate_identity_insert_value(&item.value)?;
12846            } else if has_variable_kind {
12847                // Output: SET [VARIABLE] <name> = <value>
12848                // VARIABLE keyword already written above if dialect requires it
12849                if let Some(ns) = name_str {
12850                    self.write(ns);
12851                } else {
12852                    self.generate_expression(&item.name)?;
12853                }
12854                self.write(" = ");
12855                self.generate_set_value(&item.value)?;
12856            } else if is_value_only {
12857                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
12858                self.generate_expression(&item.name)?;
12859            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
12860                // SET key value without = (TSQL style)
12861                self.generate_expression(&item.name)?;
12862                self.write_space();
12863                self.generate_set_value(&item.value)?;
12864            } else {
12865                // Standard: variable = value
12866                // SET item names should not be quoted (they are config parameter names, not column refs)
12867                match &item.name {
12868                    Expression::Identifier(id) => {
12869                        self.write(&id.name);
12870                    }
12871                    _ => {
12872                        self.generate_expression(&item.name)?;
12873                    }
12874                }
12875                self.write(" = ");
12876                self.generate_set_value(&item.value)?;
12877            }
12878        }
12879
12880        Ok(())
12881    }
12882
12883    fn generate_identity_insert_value(&mut self, value: &Expression) -> Result<()> {
12884        if let Expression::Tuple(tuple) = value {
12885            if tuple.expressions.len() == 2 {
12886                self.generate_expression(&tuple.expressions[0])?;
12887                self.write_space();
12888                self.generate_set_value(&tuple.expressions[1])?;
12889                return Ok(());
12890            }
12891        }
12892
12893        self.generate_set_value(value)
12894    }
12895
12896    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
12897    /// directly to avoid reserved keyword quoting.
12898    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
12899        if let Expression::Identifier(id) = value {
12900            match id.name.as_str() {
12901                "DEFAULT" | "ON" | "OFF" => {
12902                    self.write_keyword(&id.name);
12903                    return Ok(());
12904                }
12905                _ => {}
12906            }
12907        }
12908        self.generate_expression(value)
12909    }
12910
12911    // ==================== Phase 4: Additional DDL Generation ====================
12912
12913    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
12914        self.write_keyword("ALTER");
12915        // MySQL modifiers before VIEW
12916        if let Some(ref algorithm) = av.algorithm {
12917            self.write_space();
12918            self.write_keyword("ALGORITHM");
12919            self.write(" = ");
12920            self.write_keyword(algorithm);
12921        }
12922        if let Some(ref definer) = av.definer {
12923            self.write_space();
12924            self.write_keyword("DEFINER");
12925            self.write(" = ");
12926            self.write(definer);
12927        }
12928        if let Some(ref sql_security) = av.sql_security {
12929            self.write_space();
12930            self.write_keyword("SQL SECURITY");
12931            self.write(" = ");
12932            self.write_keyword(sql_security);
12933        }
12934        self.write_space();
12935        self.write_keyword("VIEW");
12936        self.write_space();
12937        self.generate_table(&av.name)?;
12938
12939        // Hive: Column aliases with optional COMMENT
12940        if !av.columns.is_empty() {
12941            self.write(" (");
12942            for (i, col) in av.columns.iter().enumerate() {
12943                if i > 0 {
12944                    self.write(", ");
12945                }
12946                self.generate_identifier(&col.name)?;
12947                if let Some(ref comment) = col.comment {
12948                    self.write_space();
12949                    self.write_keyword("COMMENT");
12950                    self.write(" ");
12951                    self.generate_string_literal(comment)?;
12952                }
12953            }
12954            self.write(")");
12955        }
12956
12957        // TSQL: WITH option before actions
12958        if let Some(ref opt) = av.with_option {
12959            self.write_space();
12960            self.write_keyword("WITH");
12961            self.write_space();
12962            self.write_keyword(opt);
12963        }
12964
12965        for action in &av.actions {
12966            self.write_space();
12967            match action {
12968                AlterViewAction::Rename(new_name) => {
12969                    self.write_keyword("RENAME TO");
12970                    self.write_space();
12971                    self.generate_table(new_name)?;
12972                }
12973                AlterViewAction::OwnerTo(owner) => {
12974                    self.write_keyword("OWNER TO");
12975                    self.write_space();
12976                    self.generate_identifier(owner)?;
12977                }
12978                AlterViewAction::SetSchema(schema) => {
12979                    self.write_keyword("SET SCHEMA");
12980                    self.write_space();
12981                    self.generate_identifier(schema)?;
12982                }
12983                AlterViewAction::SetAuthorization(auth) => {
12984                    self.write_keyword("SET AUTHORIZATION");
12985                    self.write_space();
12986                    self.write(auth);
12987                }
12988                AlterViewAction::AlterColumn { name, action } => {
12989                    self.write_keyword("ALTER COLUMN");
12990                    self.write_space();
12991                    self.generate_identifier(name)?;
12992                    self.write_space();
12993                    self.generate_alter_column_action(action)?;
12994                }
12995                AlterViewAction::AsSelect(query) => {
12996                    self.write_keyword("AS");
12997                    self.write_space();
12998                    self.generate_expression(query)?;
12999                }
13000                AlterViewAction::SetTblproperties(props) => {
13001                    self.write_keyword("SET TBLPROPERTIES");
13002                    self.write(" (");
13003                    for (i, (key, value)) in props.iter().enumerate() {
13004                        if i > 0 {
13005                            self.write(", ");
13006                        }
13007                        self.generate_string_literal(key)?;
13008                        self.write("=");
13009                        self.generate_string_literal(value)?;
13010                    }
13011                    self.write(")");
13012                }
13013                AlterViewAction::UnsetTblproperties(keys) => {
13014                    self.write_keyword("UNSET TBLPROPERTIES");
13015                    self.write(" (");
13016                    for (i, key) in keys.iter().enumerate() {
13017                        if i > 0 {
13018                            self.write(", ");
13019                        }
13020                        self.generate_string_literal(key)?;
13021                    }
13022                    self.write(")");
13023                }
13024            }
13025        }
13026
13027        Ok(())
13028    }
13029
13030    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
13031        self.write_keyword("ALTER INDEX");
13032        self.write_space();
13033        self.generate_identifier(&ai.name)?;
13034
13035        if let Some(table) = &ai.table {
13036            self.write_space();
13037            self.write_keyword("ON");
13038            self.write_space();
13039            self.generate_table(table)?;
13040        }
13041
13042        for action in &ai.actions {
13043            self.write_space();
13044            match action {
13045                AlterIndexAction::Rename(new_name) => {
13046                    self.write_keyword("RENAME TO");
13047                    self.write_space();
13048                    self.generate_identifier(new_name)?;
13049                }
13050                AlterIndexAction::SetTablespace(tablespace) => {
13051                    self.write_keyword("SET TABLESPACE");
13052                    self.write_space();
13053                    self.generate_identifier(tablespace)?;
13054                }
13055                AlterIndexAction::Visible(visible) => {
13056                    if *visible {
13057                        self.write_keyword("VISIBLE");
13058                    } else {
13059                        self.write_keyword("INVISIBLE");
13060                    }
13061                }
13062            }
13063        }
13064
13065        Ok(())
13066    }
13067
13068    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
13069        // Output leading comments
13070        for comment in &cs.leading_comments {
13071            self.write_formatted_comment(comment);
13072            self.write_space();
13073        }
13074
13075        // Athena: CREATE SCHEMA uses Hive engine (backticks)
13076        let saved_athena_hive_context = self.athena_hive_context;
13077        if matches!(
13078            self.config.dialect,
13079            Some(crate::dialects::DialectType::Athena)
13080        ) {
13081            self.athena_hive_context = true;
13082        }
13083
13084        self.write_keyword("CREATE SCHEMA");
13085
13086        if cs.if_not_exists {
13087            self.write_space();
13088            self.write_keyword("IF NOT EXISTS");
13089        }
13090
13091        self.write_space();
13092        for (i, part) in cs.name.iter().enumerate() {
13093            if i > 0 {
13094                self.write(".");
13095            }
13096            self.generate_identifier(part)?;
13097        }
13098
13099        if let Some(ref clone_parts) = cs.clone_from {
13100            self.write_keyword(" CLONE ");
13101            for (i, part) in clone_parts.iter().enumerate() {
13102                if i > 0 {
13103                    self.write(".");
13104                }
13105                self.generate_identifier(part)?;
13106            }
13107        }
13108
13109        if let Some(ref at_clause) = cs.at_clause {
13110            self.write_space();
13111            self.generate_expression(at_clause)?;
13112        }
13113
13114        if let Some(auth) = &cs.authorization {
13115            self.write_space();
13116            self.write_keyword("AUTHORIZATION");
13117            self.write_space();
13118            self.generate_identifier(auth)?;
13119        }
13120
13121        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
13122        // Separate WITH properties from other properties
13123        let with_properties: Vec<_> = cs
13124            .properties
13125            .iter()
13126            .filter(|p| matches!(p, Expression::Property(_)))
13127            .collect();
13128        let other_properties: Vec<_> = cs
13129            .properties
13130            .iter()
13131            .filter(|p| !matches!(p, Expression::Property(_)))
13132            .collect();
13133
13134        // Generate WITH (props) if we have Property expressions
13135        if !with_properties.is_empty() {
13136            self.write_space();
13137            self.write_keyword("WITH");
13138            self.write(" (");
13139            for (i, prop) in with_properties.iter().enumerate() {
13140                if i > 0 {
13141                    self.write(", ");
13142                }
13143                self.generate_expression(prop)?;
13144            }
13145            self.write(")");
13146        }
13147
13148        // Generate other properties (like DEFAULT COLLATE)
13149        for prop in other_properties {
13150            self.write_space();
13151            self.generate_expression(prop)?;
13152        }
13153
13154        // Restore Athena Hive context
13155        self.athena_hive_context = saved_athena_hive_context;
13156
13157        Ok(())
13158    }
13159
13160    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
13161        self.write_keyword("DROP SCHEMA");
13162
13163        if ds.if_exists {
13164            self.write_space();
13165            self.write_keyword("IF EXISTS");
13166        }
13167
13168        self.write_space();
13169        self.generate_identifier(&ds.name)?;
13170
13171        if ds.cascade {
13172            self.write_space();
13173            self.write_keyword("CASCADE");
13174        }
13175
13176        Ok(())
13177    }
13178
13179    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
13180        self.write_keyword("DROP NAMESPACE");
13181
13182        if dn.if_exists {
13183            self.write_space();
13184            self.write_keyword("IF EXISTS");
13185        }
13186
13187        self.write_space();
13188        self.generate_identifier(&dn.name)?;
13189
13190        if dn.cascade {
13191            self.write_space();
13192            self.write_keyword("CASCADE");
13193        }
13194
13195        Ok(())
13196    }
13197
13198    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
13199        self.write_keyword("CREATE DATABASE");
13200
13201        if cd.if_not_exists {
13202            self.write_space();
13203            self.write_keyword("IF NOT EXISTS");
13204        }
13205
13206        self.write_space();
13207        self.generate_identifier(&cd.name)?;
13208
13209        if let Some(ref clone_src) = cd.clone_from {
13210            self.write_keyword(" CLONE ");
13211            self.generate_identifier(clone_src)?;
13212        }
13213
13214        // AT/BEFORE clause for time travel (Snowflake)
13215        if let Some(ref at_clause) = cd.at_clause {
13216            self.write_space();
13217            self.generate_expression(at_clause)?;
13218        }
13219
13220        for option in &cd.options {
13221            self.write_space();
13222            match option {
13223                DatabaseOption::CharacterSet(charset) => {
13224                    self.write_keyword("CHARACTER SET");
13225                    self.write(" = ");
13226                    self.write(&format!("'{}'", charset));
13227                }
13228                DatabaseOption::Collate(collate) => {
13229                    self.write_keyword("COLLATE");
13230                    self.write(" = ");
13231                    self.write(&format!("'{}'", collate));
13232                }
13233                DatabaseOption::Owner(owner) => {
13234                    self.write_keyword("OWNER");
13235                    self.write(" = ");
13236                    self.generate_identifier(owner)?;
13237                }
13238                DatabaseOption::Template(template) => {
13239                    self.write_keyword("TEMPLATE");
13240                    self.write(" = ");
13241                    self.generate_identifier(template)?;
13242                }
13243                DatabaseOption::Encoding(encoding) => {
13244                    self.write_keyword("ENCODING");
13245                    self.write(" = ");
13246                    self.write(&format!("'{}'", encoding));
13247                }
13248                DatabaseOption::Location(location) => {
13249                    self.write_keyword("LOCATION");
13250                    self.write(" = ");
13251                    self.write(&format!("'{}'", location));
13252                }
13253            }
13254        }
13255
13256        Ok(())
13257    }
13258
13259    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
13260        self.write_keyword("DROP DATABASE");
13261
13262        if dd.if_exists {
13263            self.write_space();
13264            self.write_keyword("IF EXISTS");
13265        }
13266
13267        self.write_space();
13268        self.generate_identifier(&dd.name)?;
13269
13270        if dd.sync {
13271            self.write_space();
13272            self.write_keyword("SYNC");
13273        }
13274
13275        Ok(())
13276    }
13277
13278    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
13279        self.write_keyword("CREATE");
13280
13281        if cf.or_alter {
13282            self.write_space();
13283            self.write_keyword("OR ALTER");
13284        } else if cf.or_replace {
13285            self.write_space();
13286            self.write_keyword("OR REPLACE");
13287        }
13288
13289        if cf.temporary {
13290            self.write_space();
13291            self.write_keyword("TEMPORARY");
13292        }
13293
13294        self.write_space();
13295        if cf.is_table_function {
13296            self.write_keyword("TABLE FUNCTION");
13297        } else {
13298            self.write_keyword("FUNCTION");
13299        }
13300
13301        if cf.if_not_exists {
13302            self.write_space();
13303            self.write_keyword("IF NOT EXISTS");
13304        }
13305
13306        self.write_space();
13307        self.generate_table(&cf.name)?;
13308        if cf.has_parens {
13309            let func_multiline = self.config.pretty
13310                && matches!(
13311                    self.config.dialect,
13312                    Some(crate::dialects::DialectType::TSQL)
13313                        | Some(crate::dialects::DialectType::Fabric)
13314                )
13315                && !cf.parameters.is_empty();
13316            if func_multiline {
13317                self.write("(\n");
13318                self.indent_level += 2;
13319                self.write_indent();
13320                self.generate_function_parameters(&cf.parameters)?;
13321                self.write("\n");
13322                self.indent_level -= 2;
13323                self.write(")");
13324            } else {
13325                self.write("(");
13326                self.generate_function_parameters(&cf.parameters)?;
13327                self.write(")");
13328            }
13329        }
13330
13331        // Output RETURNS clause (always comes first after parameters)
13332        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
13333        let use_multiline = self.config.pretty
13334            && matches!(
13335                self.config.dialect,
13336                Some(crate::dialects::DialectType::BigQuery)
13337                    | Some(crate::dialects::DialectType::TSQL)
13338                    | Some(crate::dialects::DialectType::Fabric)
13339            );
13340
13341        if cf.language_first {
13342            // LANGUAGE first, then SQL data access, then RETURNS
13343            if let Some(lang) = &cf.language {
13344                if use_multiline {
13345                    self.write_newline();
13346                } else {
13347                    self.write_space();
13348                }
13349                self.write_keyword("LANGUAGE");
13350                self.write_space();
13351                self.write(lang);
13352            }
13353
13354            // SQL data access comes after LANGUAGE in this case
13355            if let Some(sql_data) = &cf.sql_data_access {
13356                self.write_space();
13357                match sql_data {
13358                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
13359                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
13360                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
13361                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
13362                }
13363            }
13364
13365            if let Some(ref rtb) = cf.returns_table_body {
13366                if use_multiline {
13367                    self.write_newline();
13368                } else {
13369                    self.write_space();
13370                }
13371                self.write_keyword("RETURNS");
13372                self.write_space();
13373                self.write(rtb);
13374            } else if let Some(return_type) = &cf.return_type {
13375                if use_multiline {
13376                    self.write_newline();
13377                } else {
13378                    self.write_space();
13379                }
13380                self.write_keyword("RETURNS");
13381                self.write_space();
13382                self.generate_function_return_type(return_type)?;
13383            }
13384        } else {
13385            // RETURNS first (default)
13386            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
13387            let is_duckdb = matches!(
13388                self.config.dialect,
13389                Some(crate::dialects::DialectType::DuckDB)
13390            );
13391            if let Some(ref rtb) = cf.returns_table_body {
13392                if !(is_duckdb && rtb.is_empty()) {
13393                    if use_multiline {
13394                        self.write_newline();
13395                    } else {
13396                        self.write_space();
13397                    }
13398                    self.write_keyword("RETURNS");
13399                    self.write_space();
13400                    self.write(rtb);
13401                }
13402            } else if let Some(return_type) = &cf.return_type {
13403                // DuckDB: skip all RETURNS (DuckDB macros don't use RETURNS clause)
13404                if !is_duckdb {
13405                    let is_table_return = matches!(return_type, crate::expressions::DataType::Custom { ref name } if name.eq_ignore_ascii_case("TABLE"));
13406                    if use_multiline {
13407                        self.write_newline();
13408                    } else {
13409                        self.write_space();
13410                    }
13411                    self.write_keyword("RETURNS");
13412                    self.write_space();
13413                    if is_table_return {
13414                        self.write_keyword("TABLE");
13415                    } else {
13416                        self.generate_function_return_type(return_type)?;
13417                    }
13418                }
13419            }
13420        }
13421
13422        // If we have property_order, use it to output properties in original order
13423        if !cf.property_order.is_empty() {
13424            // For BigQuery, OPTIONS must come before AS - reorder if needed
13425            let is_bigquery = matches!(
13426                self.config.dialect,
13427                Some(crate::dialects::DialectType::BigQuery)
13428            );
13429            let is_postgres = matches!(
13430                self.config.dialect,
13431                Some(crate::dialects::DialectType::PostgreSQL)
13432            );
13433            let property_order = if is_bigquery {
13434                // Move Options before As if both are present
13435                let mut reordered = Vec::new();
13436                let mut has_as = false;
13437                let mut has_options = false;
13438                for prop in &cf.property_order {
13439                    match prop {
13440                        FunctionPropertyKind::As => has_as = true,
13441                        FunctionPropertyKind::Options => has_options = true,
13442                        _ => {}
13443                    }
13444                }
13445                if has_as && has_options {
13446                    // Output all props except As and Options, then Options, then As
13447                    for prop in &cf.property_order {
13448                        if *prop != FunctionPropertyKind::As
13449                            && *prop != FunctionPropertyKind::Options
13450                        {
13451                            reordered.push(*prop);
13452                        }
13453                    }
13454                    reordered.push(FunctionPropertyKind::Options);
13455                    reordered.push(FunctionPropertyKind::As);
13456                    reordered
13457                } else {
13458                    cf.property_order.clone()
13459                }
13460            } else if is_postgres
13461                && cf.property_order.contains(&FunctionPropertyKind::As)
13462                && cf.property_order.contains(&FunctionPropertyKind::NullInput)
13463            {
13464                let mut reordered: Vec<_> = cf
13465                    .property_order
13466                    .iter()
13467                    .copied()
13468                    .filter(|prop| *prop != FunctionPropertyKind::As)
13469                    .collect();
13470                reordered.push(FunctionPropertyKind::As);
13471                reordered
13472            } else {
13473                cf.property_order.clone()
13474            };
13475
13476            for prop in &property_order {
13477                match prop {
13478                    FunctionPropertyKind::Set => {
13479                        self.generate_function_set_options(cf)?;
13480                    }
13481                    FunctionPropertyKind::As => {
13482                        self.generate_function_body(cf)?;
13483                    }
13484                    FunctionPropertyKind::Using => {
13485                        self.generate_function_using_resources(cf)?;
13486                    }
13487                    FunctionPropertyKind::Language => {
13488                        if !cf.language_first {
13489                            // Only output here if not already output above
13490                            if let Some(lang) = &cf.language {
13491                                // Only BigQuery uses multiline formatting
13492                                let use_multiline = self.config.pretty
13493                                    && matches!(
13494                                        self.config.dialect,
13495                                        Some(crate::dialects::DialectType::BigQuery)
13496                                    );
13497                                if use_multiline {
13498                                    self.write_newline();
13499                                } else {
13500                                    self.write_space();
13501                                }
13502                                self.write_keyword("LANGUAGE");
13503                                self.write_space();
13504                                self.write(lang);
13505                            }
13506                        }
13507                    }
13508                    FunctionPropertyKind::Determinism => {
13509                        self.generate_function_determinism(cf)?;
13510                    }
13511                    FunctionPropertyKind::NullInput => {
13512                        self.generate_function_null_input(cf)?;
13513                    }
13514                    FunctionPropertyKind::Security => {
13515                        self.generate_function_security(cf)?;
13516                    }
13517                    FunctionPropertyKind::SqlDataAccess => {
13518                        if !cf.language_first {
13519                            // Only output here if not already output above
13520                            self.generate_function_sql_data_access(cf)?;
13521                        }
13522                    }
13523                    FunctionPropertyKind::Options => {
13524                        if !cf.options.is_empty() {
13525                            self.write_space();
13526                            self.generate_options_clause(&cf.options)?;
13527                        }
13528                    }
13529                    FunctionPropertyKind::Environment => {
13530                        if !cf.environment.is_empty() {
13531                            self.write_space();
13532                            self.generate_environment_clause(&cf.environment)?;
13533                        }
13534                    }
13535                    FunctionPropertyKind::Handler => {
13536                        if let Some(ref h) = cf.handler {
13537                            self.write_space();
13538                            self.write_keyword("HANDLER");
13539                            if cf.handler_uses_eq {
13540                                self.write(" = ");
13541                            } else {
13542                                self.write_space();
13543                            }
13544                            self.write("'");
13545                            self.write(h);
13546                            self.write("'");
13547                        }
13548                    }
13549                    FunctionPropertyKind::RuntimeVersion => {
13550                        if let Some(ref runtime_version) = cf.runtime_version {
13551                            self.write_space();
13552                            self.write_keyword("RUNTIME_VERSION");
13553                            self.write("='");
13554                            self.write(runtime_version);
13555                            self.write("'");
13556                        }
13557                    }
13558                    FunctionPropertyKind::Packages => {
13559                        if let Some(ref packages) = cf.packages {
13560                            self.write_space();
13561                            self.write_keyword("PACKAGES");
13562                            self.write("=(");
13563                            for (i, package) in packages.iter().enumerate() {
13564                                if i > 0 {
13565                                    self.write(", ");
13566                                }
13567                                self.write("'");
13568                                self.write(package);
13569                                self.write("'");
13570                            }
13571                            self.write(")");
13572                        }
13573                    }
13574                    FunctionPropertyKind::ParameterStyle => {
13575                        if let Some(ref ps) = cf.parameter_style {
13576                            self.write_space();
13577                            self.write_keyword("PARAMETER STYLE");
13578                            self.write_space();
13579                            self.write_keyword(ps);
13580                        }
13581                    }
13582                }
13583            }
13584
13585            // Output OPTIONS if not tracked in property_order (legacy)
13586            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
13587            {
13588                self.write_space();
13589                self.generate_options_clause(&cf.options)?;
13590            }
13591
13592            // Output ENVIRONMENT if not tracked in property_order (legacy)
13593            if !cf.environment.is_empty()
13594                && !cf
13595                    .property_order
13596                    .contains(&FunctionPropertyKind::Environment)
13597            {
13598                self.write_space();
13599                self.generate_environment_clause(&cf.environment)?;
13600            }
13601        } else {
13602            // Legacy behavior when property_order is empty
13603            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
13604            if matches!(
13605                self.config.dialect,
13606                Some(crate::dialects::DialectType::BigQuery)
13607            ) {
13608                self.generate_function_determinism(cf)?;
13609            }
13610
13611            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
13612            let use_multiline = self.config.pretty
13613                && matches!(
13614                    self.config.dialect,
13615                    Some(crate::dialects::DialectType::BigQuery)
13616                );
13617
13618            if !cf.language_first {
13619                if let Some(lang) = &cf.language {
13620                    if use_multiline {
13621                        self.write_newline();
13622                    } else {
13623                        self.write_space();
13624                    }
13625                    self.write_keyword("LANGUAGE");
13626                    self.write_space();
13627                    self.write(lang);
13628                }
13629
13630                // SQL data access characteristic comes after LANGUAGE
13631                self.generate_function_sql_data_access(cf)?;
13632            }
13633
13634            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
13635            if !matches!(
13636                self.config.dialect,
13637                Some(crate::dialects::DialectType::BigQuery)
13638            ) {
13639                self.generate_function_determinism(cf)?;
13640            }
13641
13642            self.generate_function_null_input(cf)?;
13643            self.generate_function_security(cf)?;
13644            self.generate_function_set_options(cf)?;
13645
13646            // BigQuery: OPTIONS (key=value, ...) - comes before AS
13647            if !cf.options.is_empty() {
13648                self.write_space();
13649                self.generate_options_clause(&cf.options)?;
13650            }
13651
13652            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
13653            if !cf.environment.is_empty() {
13654                self.write_space();
13655                self.generate_environment_clause(&cf.environment)?;
13656            }
13657
13658            if let Some(ref h) = cf.handler {
13659                self.write_space();
13660                self.write_keyword("HANDLER");
13661                if cf.handler_uses_eq {
13662                    self.write(" = ");
13663                } else {
13664                    self.write_space();
13665                }
13666                self.write("'");
13667                self.write(h);
13668                self.write("'");
13669            }
13670
13671            if let Some(ref runtime_version) = cf.runtime_version {
13672                self.write_space();
13673                self.write_keyword("RUNTIME_VERSION");
13674                self.write("='");
13675                self.write(runtime_version);
13676                self.write("'");
13677            }
13678
13679            if let Some(ref packages) = cf.packages {
13680                self.write_space();
13681                self.write_keyword("PACKAGES");
13682                self.write("=(");
13683                for (i, package) in packages.iter().enumerate() {
13684                    if i > 0 {
13685                        self.write(", ");
13686                    }
13687                    self.write("'");
13688                    self.write(package);
13689                    self.write("'");
13690                }
13691                self.write(")");
13692            }
13693
13694            self.generate_function_body(cf)?;
13695            self.generate_function_using_resources(cf)?;
13696        }
13697
13698        Ok(())
13699    }
13700
13701    fn generate_function_return_type(&mut self, return_type: &DataType) -> Result<()> {
13702        if matches!(
13703            self.config.dialect,
13704            Some(crate::dialects::DialectType::PostgreSQL)
13705        ) {
13706            if let DataType::Custom { name } = return_type {
13707                if name.eq_ignore_ascii_case("integer") {
13708                    self.write_keyword("INT");
13709                    return Ok(());
13710                }
13711            }
13712        }
13713
13714        self.generate_data_type(return_type)
13715    }
13716
13717    /// Generate SET options for CREATE FUNCTION
13718    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
13719        for opt in &cf.set_options {
13720            self.write_space();
13721            self.write_keyword("SET");
13722            self.write_space();
13723            self.write(&opt.name);
13724            match &opt.value {
13725                FunctionSetValue::Value { value, use_to } => {
13726                    if *use_to {
13727                        self.write(" TO ");
13728                    } else {
13729                        self.write(" = ");
13730                    }
13731                    self.write(value);
13732                }
13733                FunctionSetValue::FromCurrent => {
13734                    self.write_space();
13735                    self.write_keyword("FROM CURRENT");
13736                }
13737            }
13738        }
13739        Ok(())
13740    }
13741
13742    fn generate_function_using_resources(&mut self, cf: &CreateFunction) -> Result<()> {
13743        if cf.using_resources.is_empty() {
13744            return Ok(());
13745        }
13746
13747        self.write_space();
13748        self.write_keyword("USING");
13749        for resource in &cf.using_resources {
13750            self.write_space();
13751            self.write_keyword(&resource.kind);
13752            self.write_space();
13753            self.generate_string_literal(&resource.uri)?;
13754        }
13755        Ok(())
13756    }
13757
13758    /// Generate function body (AS clause)
13759    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
13760        if let Some(body) = &cf.body {
13761            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
13762            self.write_space();
13763            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
13764            let use_multiline = self.config.pretty
13765                && matches!(
13766                    self.config.dialect,
13767                    Some(crate::dialects::DialectType::BigQuery)
13768                );
13769            match body {
13770                FunctionBody::Block(block) => {
13771                    self.write_keyword("AS");
13772                    if matches!(
13773                        self.config.dialect,
13774                        Some(crate::dialects::DialectType::TSQL)
13775                    ) {
13776                        self.write(" BEGIN ");
13777                        self.write(block);
13778                        self.write(" END");
13779                    } else if matches!(
13780                        self.config.dialect,
13781                        Some(crate::dialects::DialectType::PostgreSQL)
13782                    ) {
13783                        self.write(" $$");
13784                        self.write(block);
13785                        self.write("$$");
13786                    } else {
13787                        // Escape content for single-quoted output
13788                        let escaped = self.escape_block_for_single_quote(block);
13789                        // In BigQuery pretty mode, body content goes on new line
13790                        if use_multiline {
13791                            self.write_newline();
13792                        } else {
13793                            self.write(" ");
13794                        }
13795                        self.write("'");
13796                        self.write(&escaped);
13797                        self.write("'");
13798                    }
13799                }
13800                FunctionBody::StringLiteral(s) => {
13801                    self.write_keyword("AS");
13802                    // In BigQuery pretty mode, body content goes on new line
13803                    if use_multiline {
13804                        self.write_newline();
13805                    } else {
13806                        self.write(" ");
13807                    }
13808                    self.write("'");
13809                    self.write(s);
13810                    self.write("'");
13811                }
13812                FunctionBody::Expression(expr) => {
13813                    self.write_keyword("AS");
13814                    self.write_space();
13815                    self.generate_expression(expr)?;
13816                }
13817                FunctionBody::External(name) => {
13818                    self.write_keyword("EXTERNAL NAME");
13819                    self.write(" '");
13820                    self.write(name);
13821                    self.write("'");
13822                }
13823                FunctionBody::Return(expr) => {
13824                    if matches!(
13825                        self.config.dialect,
13826                        Some(crate::dialects::DialectType::DuckDB)
13827                    ) {
13828                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
13829                        self.write_keyword("AS");
13830                        self.write_space();
13831                        // Check both returns_table_body marker and return_type = Custom "TABLE"
13832                        let is_table_return = cf.returns_table_body.is_some()
13833                            || matches!(&cf.return_type, Some(crate::expressions::DataType::Custom { ref name }) if name.eq_ignore_ascii_case("TABLE"));
13834                        if is_table_return {
13835                            self.write_keyword("TABLE");
13836                            self.write_space();
13837                        }
13838                        self.generate_expression(expr)?;
13839                    } else {
13840                        if self.config.create_function_return_as {
13841                            self.write_keyword("AS");
13842                            // TSQL pretty: newline between AS and RETURN
13843                            if self.config.pretty
13844                                && matches!(
13845                                    self.config.dialect,
13846                                    Some(crate::dialects::DialectType::TSQL)
13847                                        | Some(crate::dialects::DialectType::Fabric)
13848                                )
13849                            {
13850                                self.write_newline();
13851                            } else {
13852                                self.write_space();
13853                            }
13854                        }
13855                        self.write_keyword("RETURN");
13856                        self.write_space();
13857                        self.generate_expression(expr)?;
13858                    }
13859                }
13860                FunctionBody::Statements(stmts) => {
13861                    self.write_keyword("AS");
13862                    self.write(" BEGIN ");
13863                    for (i, stmt) in stmts.iter().enumerate() {
13864                        if i > 0 {
13865                            self.write(" ");
13866                        }
13867                        self.generate_expression(stmt)?;
13868                        self.write(";");
13869                    }
13870                    self.write(" END");
13871                }
13872                FunctionBody::RawBlock(text) => {
13873                    self.write_newline();
13874                    self.write(text);
13875                }
13876                FunctionBody::DollarQuoted { content, tag } => {
13877                    self.write_keyword("AS");
13878                    self.write(" ");
13879                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13880                    let supports_dollar_quoting = matches!(
13881                        self.config.dialect,
13882                        Some(crate::dialects::DialectType::PostgreSQL)
13883                            | Some(crate::dialects::DialectType::Databricks)
13884                            | Some(crate::dialects::DialectType::Redshift)
13885                            | Some(crate::dialects::DialectType::DuckDB)
13886                    );
13887                    if supports_dollar_quoting {
13888                        // Output in dollar-quoted format
13889                        self.write("$");
13890                        if let Some(t) = tag {
13891                            self.write(t);
13892                        }
13893                        self.write("$");
13894                        self.write(content);
13895                        self.write("$");
13896                        if let Some(t) = tag {
13897                            self.write(t);
13898                        }
13899                        self.write("$");
13900                    } else {
13901                        // Convert to single-quoted string for other dialects
13902                        let escaped = self.escape_block_for_single_quote(content);
13903                        self.write("'");
13904                        self.write(&escaped);
13905                        self.write("'");
13906                    }
13907                }
13908            }
13909        }
13910        Ok(())
13911    }
13912
13913    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
13914    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
13915        if let Some(det) = cf.deterministic {
13916            self.write_space();
13917            if matches!(
13918                self.config.dialect,
13919                Some(crate::dialects::DialectType::BigQuery)
13920            ) {
13921                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
13922                if det {
13923                    self.write_keyword("DETERMINISTIC");
13924                } else {
13925                    self.write_keyword("NOT DETERMINISTIC");
13926                }
13927            } else {
13928                // PostgreSQL and others use IMMUTABLE/VOLATILE
13929                if det {
13930                    self.write_keyword("IMMUTABLE");
13931                } else {
13932                    self.write_keyword("VOLATILE");
13933                }
13934            }
13935        }
13936        Ok(())
13937    }
13938
13939    /// Generate null input handling clause
13940    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
13941        if let Some(returns_null) = cf.returns_null_on_null_input {
13942            self.write_space();
13943            if returns_null {
13944                if cf.strict {
13945                    self.write_keyword("STRICT");
13946                } else {
13947                    self.write_keyword("RETURNS NULL ON NULL INPUT");
13948                }
13949            } else {
13950                self.write_keyword("CALLED ON NULL INPUT");
13951            }
13952        }
13953        Ok(())
13954    }
13955
13956    /// Generate security clause
13957    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
13958        if let Some(security) = &cf.security {
13959            self.write_space();
13960            // MySQL uses SQL SECURITY prefix
13961            if matches!(
13962                self.config.dialect,
13963                Some(crate::dialects::DialectType::MySQL)
13964            ) {
13965                self.write_keyword("SQL SECURITY");
13966            } else {
13967                self.write_keyword("SECURITY");
13968            }
13969            self.write_space();
13970            match security {
13971                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13972                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13973                FunctionSecurity::None => self.write_keyword("NONE"),
13974            }
13975        }
13976        Ok(())
13977    }
13978
13979    /// Generate SQL data access clause
13980    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
13981        if let Some(sql_data) = &cf.sql_data_access {
13982            self.write_space();
13983            match sql_data {
13984                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
13985                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
13986                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
13987                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
13988            }
13989        }
13990        Ok(())
13991    }
13992
13993    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
13994        for (i, param) in params.iter().enumerate() {
13995            if i > 0 {
13996                self.write(", ");
13997            }
13998
13999            if let Some(mode) = &param.mode {
14000                if let Some(text) = &param.mode_text {
14001                    self.write(text);
14002                } else {
14003                    match mode {
14004                        ParameterMode::In => self.write_keyword("IN"),
14005                        ParameterMode::Out => self.write_keyword("OUT"),
14006                        ParameterMode::InOut => self.write_keyword("INOUT"),
14007                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
14008                    }
14009                }
14010                self.write_space();
14011            }
14012
14013            if let Some(name) = &param.name {
14014                self.generate_identifier(name)?;
14015                // Skip space and type for empty Custom types (e.g., DuckDB macros)
14016                let skip_type =
14017                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
14018                if !skip_type {
14019                    self.write_space();
14020                    self.generate_data_type(&param.data_type)?;
14021                }
14022            } else {
14023                self.generate_data_type(&param.data_type)?;
14024            }
14025
14026            if let Some(default) = &param.default {
14027                if self.config.parameter_default_equals {
14028                    self.write(" = ");
14029                } else {
14030                    self.write(" DEFAULT ");
14031                }
14032                self.generate_expression(default)?;
14033            }
14034        }
14035
14036        Ok(())
14037    }
14038
14039    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
14040        self.write_keyword("DROP FUNCTION");
14041
14042        if df.if_exists {
14043            self.write_space();
14044            self.write_keyword("IF EXISTS");
14045        }
14046
14047        self.write_space();
14048        self.generate_table(&df.name)?;
14049
14050        if let Some(params) = &df.parameters {
14051            self.write(" (");
14052            for (i, dt) in params.iter().enumerate() {
14053                if i > 0 {
14054                    self.write(", ");
14055                }
14056                self.generate_data_type(dt)?;
14057            }
14058            self.write(")");
14059        }
14060
14061        if df.cascade {
14062            self.write_space();
14063            self.write_keyword("CASCADE");
14064        }
14065
14066        Ok(())
14067    }
14068
14069    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
14070        self.write_keyword("CREATE");
14071
14072        if cp.or_alter {
14073            self.write_space();
14074            self.write_keyword("OR ALTER");
14075        } else if cp.or_replace {
14076            self.write_space();
14077            self.write_keyword("OR REPLACE");
14078        }
14079
14080        self.write_space();
14081        if cp.use_proc_keyword {
14082            self.write_keyword("PROC");
14083        } else {
14084            self.write_keyword("PROCEDURE");
14085        }
14086
14087        if cp.if_not_exists {
14088            self.write_space();
14089            self.write_keyword("IF NOT EXISTS");
14090        }
14091
14092        self.write_space();
14093        self.generate_table(&cp.name)?;
14094        if cp.has_parens {
14095            self.write("(");
14096            self.generate_function_parameters(&cp.parameters)?;
14097            self.write(")");
14098        } else if !cp.parameters.is_empty() {
14099            // TSQL: unparenthesized parameters
14100            self.write_space();
14101            self.generate_function_parameters(&cp.parameters)?;
14102        }
14103
14104        // RETURNS clause (Snowflake)
14105        if let Some(return_type) = &cp.return_type {
14106            self.write_space();
14107            self.write_keyword("RETURNS");
14108            self.write_space();
14109            self.generate_data_type(return_type)?;
14110        }
14111
14112        // EXECUTE AS clause (Snowflake)
14113        if let Some(execute_as) = &cp.execute_as {
14114            self.write_space();
14115            self.write_keyword("EXECUTE AS");
14116            self.write_space();
14117            self.write_keyword(execute_as);
14118        }
14119
14120        if let Some(lang) = &cp.language {
14121            self.write_space();
14122            self.write_keyword("LANGUAGE");
14123            self.write_space();
14124            self.write(lang);
14125        }
14126
14127        if let Some(security) = &cp.security {
14128            self.write_space();
14129            self.write_keyword("SECURITY");
14130            self.write_space();
14131            match security {
14132                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
14133                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
14134                FunctionSecurity::None => self.write_keyword("NONE"),
14135            }
14136        }
14137
14138        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
14139        if !cp.with_options.is_empty() {
14140            self.write_space();
14141            self.write_keyword("WITH");
14142            self.write_space();
14143            for (i, opt) in cp.with_options.iter().enumerate() {
14144                if i > 0 {
14145                    self.write(", ");
14146                }
14147                self.write(opt);
14148            }
14149        }
14150
14151        if let Some(body) = &cp.body {
14152            self.write_space();
14153            match body {
14154                FunctionBody::Block(block) => {
14155                    self.write_keyword("AS");
14156                    if matches!(
14157                        self.config.dialect,
14158                        Some(crate::dialects::DialectType::TSQL)
14159                    ) {
14160                        self.write(" BEGIN ");
14161                        self.write(block);
14162                        self.write(" END");
14163                    } else if matches!(
14164                        self.config.dialect,
14165                        Some(crate::dialects::DialectType::PostgreSQL)
14166                    ) {
14167                        self.write(" $$");
14168                        self.write(block);
14169                        self.write("$$");
14170                    } else {
14171                        // Escape content for single-quoted output
14172                        let escaped = self.escape_block_for_single_quote(block);
14173                        self.write(" '");
14174                        self.write(&escaped);
14175                        self.write("'");
14176                    }
14177                }
14178                FunctionBody::StringLiteral(s) => {
14179                    self.write_keyword("AS");
14180                    self.write(" '");
14181                    self.write(s);
14182                    self.write("'");
14183                }
14184                FunctionBody::Expression(expr) => {
14185                    self.write_keyword("AS");
14186                    self.write_space();
14187                    self.generate_expression(expr)?;
14188                }
14189                FunctionBody::External(name) => {
14190                    self.write_keyword("EXTERNAL NAME");
14191                    self.write(" '");
14192                    self.write(name);
14193                    self.write("'");
14194                }
14195                FunctionBody::Return(expr) => {
14196                    self.write_keyword("RETURN");
14197                    self.write_space();
14198                    self.generate_expression(expr)?;
14199                }
14200                FunctionBody::Statements(stmts) => {
14201                    self.write_keyword("AS");
14202                    self.write(" BEGIN ");
14203                    for (i, stmt) in stmts.iter().enumerate() {
14204                        if i > 0 {
14205                            self.write(" ");
14206                        }
14207                        self.generate_expression(stmt)?;
14208                        self.write(";");
14209                    }
14210                    self.write(" END");
14211                }
14212                FunctionBody::RawBlock(text) => {
14213                    self.write_newline();
14214                    self.write(text);
14215                }
14216                FunctionBody::DollarQuoted { content, tag } => {
14217                    self.write_keyword("AS");
14218                    self.write(" ");
14219                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
14220                    let supports_dollar_quoting = matches!(
14221                        self.config.dialect,
14222                        Some(crate::dialects::DialectType::PostgreSQL)
14223                            | Some(crate::dialects::DialectType::Databricks)
14224                            | Some(crate::dialects::DialectType::Redshift)
14225                            | Some(crate::dialects::DialectType::DuckDB)
14226                    );
14227                    if supports_dollar_quoting {
14228                        // Output in dollar-quoted format
14229                        self.write("$");
14230                        if let Some(t) = tag {
14231                            self.write(t);
14232                        }
14233                        self.write("$");
14234                        self.write(content);
14235                        self.write("$");
14236                        if let Some(t) = tag {
14237                            self.write(t);
14238                        }
14239                        self.write("$");
14240                    } else {
14241                        // Convert to single-quoted string for other dialects
14242                        let escaped = self.escape_block_for_single_quote(content);
14243                        self.write("'");
14244                        self.write(&escaped);
14245                        self.write("'");
14246                    }
14247                }
14248            }
14249        }
14250
14251        Ok(())
14252    }
14253
14254    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
14255        self.write_keyword("DROP PROCEDURE");
14256
14257        if dp.if_exists {
14258            self.write_space();
14259            self.write_keyword("IF EXISTS");
14260        }
14261
14262        self.write_space();
14263        self.generate_table(&dp.name)?;
14264
14265        if let Some(params) = &dp.parameters {
14266            self.write(" (");
14267            for (i, dt) in params.iter().enumerate() {
14268                if i > 0 {
14269                    self.write(", ");
14270                }
14271                self.generate_data_type(dt)?;
14272            }
14273            self.write(")");
14274        }
14275
14276        if dp.cascade {
14277            self.write_space();
14278            self.write_keyword("CASCADE");
14279        }
14280
14281        Ok(())
14282    }
14283
14284    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
14285        self.write_keyword("CREATE");
14286
14287        if cs.or_replace {
14288            self.write_space();
14289            self.write_keyword("OR REPLACE");
14290        }
14291
14292        if cs.temporary {
14293            self.write_space();
14294            self.write_keyword("TEMPORARY");
14295        }
14296
14297        self.write_space();
14298        self.write_keyword("SEQUENCE");
14299
14300        if cs.if_not_exists {
14301            self.write_space();
14302            self.write_keyword("IF NOT EXISTS");
14303        }
14304
14305        self.write_space();
14306        self.generate_table(&cs.name)?;
14307
14308        // Output AS <type> if present
14309        if let Some(as_type) = &cs.as_type {
14310            self.write_space();
14311            self.write_keyword("AS");
14312            self.write_space();
14313            self.generate_data_type(as_type)?;
14314        }
14315
14316        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
14317        if let Some(comment) = &cs.comment {
14318            self.write_space();
14319            self.write_keyword("COMMENT");
14320            self.write("=");
14321            self.generate_string_literal(comment)?;
14322        }
14323
14324        // If property_order is available, use it to preserve original order
14325        if !cs.property_order.is_empty() {
14326            for prop in &cs.property_order {
14327                match prop {
14328                    SeqPropKind::Start => {
14329                        if let Some(start) = cs.start {
14330                            self.write_space();
14331                            self.write_keyword("START WITH");
14332                            self.write(&format!(" {}", start));
14333                        }
14334                    }
14335                    SeqPropKind::Increment => {
14336                        if let Some(inc) = cs.increment {
14337                            self.write_space();
14338                            self.write_keyword("INCREMENT BY");
14339                            self.write(&format!(" {}", inc));
14340                        }
14341                    }
14342                    SeqPropKind::Minvalue => {
14343                        if let Some(min) = &cs.minvalue {
14344                            self.write_space();
14345                            match min {
14346                                SequenceBound::Value(v) => {
14347                                    self.write_keyword("MINVALUE");
14348                                    self.write(&format!(" {}", v));
14349                                }
14350                                SequenceBound::None => {
14351                                    self.write_keyword("NO MINVALUE");
14352                                }
14353                            }
14354                        }
14355                    }
14356                    SeqPropKind::Maxvalue => {
14357                        if let Some(max) = &cs.maxvalue {
14358                            self.write_space();
14359                            match max {
14360                                SequenceBound::Value(v) => {
14361                                    self.write_keyword("MAXVALUE");
14362                                    self.write(&format!(" {}", v));
14363                                }
14364                                SequenceBound::None => {
14365                                    self.write_keyword("NO MAXVALUE");
14366                                }
14367                            }
14368                        }
14369                    }
14370                    SeqPropKind::Cache => {
14371                        if let Some(cache) = cs.cache {
14372                            self.write_space();
14373                            self.write_keyword("CACHE");
14374                            self.write(&format!(" {}", cache));
14375                        }
14376                    }
14377                    SeqPropKind::NoCache => {
14378                        self.write_space();
14379                        self.write_keyword("NO CACHE");
14380                    }
14381                    SeqPropKind::NoCacheWord => {
14382                        self.write_space();
14383                        self.write_keyword("NOCACHE");
14384                    }
14385                    SeqPropKind::Cycle => {
14386                        self.write_space();
14387                        self.write_keyword("CYCLE");
14388                    }
14389                    SeqPropKind::NoCycle => {
14390                        self.write_space();
14391                        self.write_keyword("NO CYCLE");
14392                    }
14393                    SeqPropKind::NoCycleWord => {
14394                        self.write_space();
14395                        self.write_keyword("NOCYCLE");
14396                    }
14397                    SeqPropKind::OwnedBy => {
14398                        // Skip OWNED BY NONE (it's a no-op)
14399                        if !cs.owned_by_none {
14400                            if let Some(owned) = &cs.owned_by {
14401                                self.write_space();
14402                                self.write_keyword("OWNED BY");
14403                                self.write_space();
14404                                self.generate_table(owned)?;
14405                            }
14406                        }
14407                    }
14408                    SeqPropKind::Order => {
14409                        self.write_space();
14410                        self.write_keyword("ORDER");
14411                    }
14412                    SeqPropKind::NoOrder => {
14413                        self.write_space();
14414                        self.write_keyword("NOORDER");
14415                    }
14416                    SeqPropKind::Comment => {
14417                        // COMMENT is output above, before property_order iteration
14418                    }
14419                    SeqPropKind::Sharing => {
14420                        if let Some(val) = &cs.sharing {
14421                            self.write_space();
14422                            self.write(&format!("SHARING={}", val));
14423                        }
14424                    }
14425                    SeqPropKind::Keep => {
14426                        self.write_space();
14427                        self.write_keyword("KEEP");
14428                    }
14429                    SeqPropKind::NoKeep => {
14430                        self.write_space();
14431                        self.write_keyword("NOKEEP");
14432                    }
14433                    SeqPropKind::Scale => {
14434                        self.write_space();
14435                        self.write_keyword("SCALE");
14436                        if let Some(modifier) = &cs.scale_modifier {
14437                            if !modifier.is_empty() {
14438                                self.write_space();
14439                                self.write_keyword(modifier);
14440                            }
14441                        }
14442                    }
14443                    SeqPropKind::NoScale => {
14444                        self.write_space();
14445                        self.write_keyword("NOSCALE");
14446                    }
14447                    SeqPropKind::Shard => {
14448                        self.write_space();
14449                        self.write_keyword("SHARD");
14450                        if let Some(modifier) = &cs.shard_modifier {
14451                            if !modifier.is_empty() {
14452                                self.write_space();
14453                                self.write_keyword(modifier);
14454                            }
14455                        }
14456                    }
14457                    SeqPropKind::NoShard => {
14458                        self.write_space();
14459                        self.write_keyword("NOSHARD");
14460                    }
14461                    SeqPropKind::Session => {
14462                        self.write_space();
14463                        self.write_keyword("SESSION");
14464                    }
14465                    SeqPropKind::Global => {
14466                        self.write_space();
14467                        self.write_keyword("GLOBAL");
14468                    }
14469                    SeqPropKind::NoMinvalueWord => {
14470                        self.write_space();
14471                        self.write_keyword("NOMINVALUE");
14472                    }
14473                    SeqPropKind::NoMaxvalueWord => {
14474                        self.write_space();
14475                        self.write_keyword("NOMAXVALUE");
14476                    }
14477                }
14478            }
14479        } else {
14480            // Fallback: default order for backwards compatibility
14481            if let Some(inc) = cs.increment {
14482                self.write_space();
14483                self.write_keyword("INCREMENT BY");
14484                self.write(&format!(" {}", inc));
14485            }
14486
14487            if let Some(min) = &cs.minvalue {
14488                self.write_space();
14489                match min {
14490                    SequenceBound::Value(v) => {
14491                        self.write_keyword("MINVALUE");
14492                        self.write(&format!(" {}", v));
14493                    }
14494                    SequenceBound::None => {
14495                        self.write_keyword("NO MINVALUE");
14496                    }
14497                }
14498            }
14499
14500            if let Some(max) = &cs.maxvalue {
14501                self.write_space();
14502                match max {
14503                    SequenceBound::Value(v) => {
14504                        self.write_keyword("MAXVALUE");
14505                        self.write(&format!(" {}", v));
14506                    }
14507                    SequenceBound::None => {
14508                        self.write_keyword("NO MAXVALUE");
14509                    }
14510                }
14511            }
14512
14513            if let Some(start) = cs.start {
14514                self.write_space();
14515                self.write_keyword("START WITH");
14516                self.write(&format!(" {}", start));
14517            }
14518
14519            if let Some(cache) = cs.cache {
14520                self.write_space();
14521                self.write_keyword("CACHE");
14522                self.write(&format!(" {}", cache));
14523            }
14524
14525            if cs.cycle {
14526                self.write_space();
14527                self.write_keyword("CYCLE");
14528            }
14529
14530            if let Some(owned) = &cs.owned_by {
14531                self.write_space();
14532                self.write_keyword("OWNED BY");
14533                self.write_space();
14534                self.generate_table(owned)?;
14535            }
14536        }
14537
14538        Ok(())
14539    }
14540
14541    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
14542        self.write_keyword("DROP SEQUENCE");
14543
14544        if ds.if_exists {
14545            self.write_space();
14546            self.write_keyword("IF EXISTS");
14547        }
14548
14549        self.write_space();
14550        self.generate_table(&ds.name)?;
14551
14552        if ds.cascade {
14553            self.write_space();
14554            self.write_keyword("CASCADE");
14555        }
14556
14557        Ok(())
14558    }
14559
14560    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
14561        self.write_keyword("ALTER SEQUENCE");
14562
14563        if als.if_exists {
14564            self.write_space();
14565            self.write_keyword("IF EXISTS");
14566        }
14567
14568        self.write_space();
14569        self.generate_table(&als.name)?;
14570
14571        if let Some(inc) = als.increment {
14572            self.write_space();
14573            self.write_keyword("INCREMENT BY");
14574            self.write(&format!(" {}", inc));
14575        }
14576
14577        if let Some(min) = &als.minvalue {
14578            self.write_space();
14579            match min {
14580                SequenceBound::Value(v) => {
14581                    self.write_keyword("MINVALUE");
14582                    self.write(&format!(" {}", v));
14583                }
14584                SequenceBound::None => {
14585                    self.write_keyword("NO MINVALUE");
14586                }
14587            }
14588        }
14589
14590        if let Some(max) = &als.maxvalue {
14591            self.write_space();
14592            match max {
14593                SequenceBound::Value(v) => {
14594                    self.write_keyword("MAXVALUE");
14595                    self.write(&format!(" {}", v));
14596                }
14597                SequenceBound::None => {
14598                    self.write_keyword("NO MAXVALUE");
14599                }
14600            }
14601        }
14602
14603        if let Some(start) = als.start {
14604            self.write_space();
14605            self.write_keyword("START WITH");
14606            self.write(&format!(" {}", start));
14607        }
14608
14609        if let Some(restart) = &als.restart {
14610            self.write_space();
14611            self.write_keyword("RESTART");
14612            if let Some(val) = restart {
14613                self.write_keyword(" WITH");
14614                self.write(&format!(" {}", val));
14615            }
14616        }
14617
14618        if let Some(cache) = als.cache {
14619            self.write_space();
14620            self.write_keyword("CACHE");
14621            self.write(&format!(" {}", cache));
14622        }
14623
14624        if let Some(cycle) = als.cycle {
14625            self.write_space();
14626            if cycle {
14627                self.write_keyword("CYCLE");
14628            } else {
14629                self.write_keyword("NO CYCLE");
14630            }
14631        }
14632
14633        if let Some(owned) = &als.owned_by {
14634            self.write_space();
14635            self.write_keyword("OWNED BY");
14636            self.write_space();
14637            if let Some(table) = owned {
14638                self.generate_table(table)?;
14639            } else {
14640                self.write_keyword("NONE");
14641            }
14642        }
14643
14644        Ok(())
14645    }
14646
14647    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
14648        self.write_keyword("CREATE");
14649
14650        if ct.or_alter {
14651            self.write_space();
14652            self.write_keyword("OR ALTER");
14653        } else if ct.or_replace {
14654            self.write_space();
14655            self.write_keyword("OR REPLACE");
14656        }
14657
14658        if ct.constraint {
14659            self.write_space();
14660            self.write_keyword("CONSTRAINT");
14661        }
14662
14663        self.write_space();
14664        self.write_keyword("TRIGGER");
14665        self.write_space();
14666        self.generate_identifier(&ct.name)?;
14667
14668        self.write_space();
14669        match ct.timing {
14670            TriggerTiming::Before => self.write_keyword("BEFORE"),
14671            TriggerTiming::After => self.write_keyword("AFTER"),
14672            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
14673        }
14674
14675        // Events
14676        for (i, event) in ct.events.iter().enumerate() {
14677            if i > 0 {
14678                self.write_keyword(" OR");
14679            }
14680            self.write_space();
14681            match event {
14682                TriggerEvent::Insert => self.write_keyword("INSERT"),
14683                TriggerEvent::Update(cols) => {
14684                    self.write_keyword("UPDATE");
14685                    if let Some(cols) = cols {
14686                        self.write_space();
14687                        self.write_keyword("OF");
14688                        for (j, col) in cols.iter().enumerate() {
14689                            if j > 0 {
14690                                self.write(",");
14691                            }
14692                            self.write_space();
14693                            self.generate_identifier(col)?;
14694                        }
14695                    }
14696                }
14697                TriggerEvent::Delete => self.write_keyword("DELETE"),
14698                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
14699            }
14700        }
14701
14702        self.write_space();
14703        self.write_keyword("ON");
14704        self.write_space();
14705        self.generate_table(&ct.table)?;
14706
14707        // Referencing clause
14708        if let Some(ref_clause) = &ct.referencing {
14709            self.write_space();
14710            self.write_keyword("REFERENCING");
14711            if let Some(old_table) = &ref_clause.old_table {
14712                self.write_space();
14713                self.write_keyword("OLD TABLE AS");
14714                self.write_space();
14715                self.generate_identifier(old_table)?;
14716            }
14717            if let Some(new_table) = &ref_clause.new_table {
14718                self.write_space();
14719                self.write_keyword("NEW TABLE AS");
14720                self.write_space();
14721                self.generate_identifier(new_table)?;
14722            }
14723            if let Some(old_row) = &ref_clause.old_row {
14724                self.write_space();
14725                self.write_keyword("OLD ROW AS");
14726                self.write_space();
14727                self.generate_identifier(old_row)?;
14728            }
14729            if let Some(new_row) = &ref_clause.new_row {
14730                self.write_space();
14731                self.write_keyword("NEW ROW AS");
14732                self.write_space();
14733                self.generate_identifier(new_row)?;
14734            }
14735        }
14736
14737        // Deferrable options for constraint triggers (must come before FOR EACH)
14738        if let Some(deferrable) = ct.deferrable {
14739            self.write_space();
14740            if deferrable {
14741                self.write_keyword("DEFERRABLE");
14742            } else {
14743                self.write_keyword("NOT DEFERRABLE");
14744            }
14745        }
14746
14747        if let Some(initially) = ct.initially_deferred {
14748            self.write_space();
14749            self.write_keyword("INITIALLY");
14750            self.write_space();
14751            if initially {
14752                self.write_keyword("DEFERRED");
14753            } else {
14754                self.write_keyword("IMMEDIATE");
14755            }
14756        }
14757
14758        if let Some(for_each) = ct.for_each {
14759            self.write_space();
14760            self.write_keyword("FOR EACH");
14761            self.write_space();
14762            match for_each {
14763                TriggerForEach::Row => self.write_keyword("ROW"),
14764                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
14765            }
14766        }
14767
14768        // When clause
14769        if let Some(when) = &ct.when {
14770            self.write_space();
14771            self.write_keyword("WHEN");
14772            if ct.when_paren {
14773                self.write(" (");
14774                self.generate_expression(when)?;
14775                self.write(")");
14776            } else {
14777                self.write_space();
14778                self.generate_expression(when)?;
14779            }
14780        }
14781
14782        // Body
14783        self.write_space();
14784        match &ct.body {
14785            TriggerBody::Execute { function, args } => {
14786                self.write_keyword("EXECUTE FUNCTION");
14787                self.write_space();
14788                self.generate_table(function)?;
14789                self.write("(");
14790                for (i, arg) in args.iter().enumerate() {
14791                    if i > 0 {
14792                        self.write(", ");
14793                    }
14794                    self.generate_expression(arg)?;
14795                }
14796                self.write(")");
14797            }
14798            TriggerBody::Block(block) => {
14799                self.write_keyword("BEGIN");
14800                self.write_space();
14801                self.write(block);
14802                self.write_space();
14803                self.write_keyword("END");
14804            }
14805        }
14806
14807        Ok(())
14808    }
14809
14810    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
14811        self.write_keyword("DROP TRIGGER");
14812
14813        if dt.if_exists {
14814            self.write_space();
14815            self.write_keyword("IF EXISTS");
14816        }
14817
14818        self.write_space();
14819        self.generate_identifier(&dt.name)?;
14820
14821        if let Some(table) = &dt.table {
14822            self.write_space();
14823            self.write_keyword("ON");
14824            self.write_space();
14825            self.generate_table(table)?;
14826        }
14827
14828        if dt.cascade {
14829            self.write_space();
14830            self.write_keyword("CASCADE");
14831        }
14832
14833        Ok(())
14834    }
14835
14836    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
14837        self.write_keyword("CREATE TYPE");
14838
14839        if ct.if_not_exists {
14840            self.write_space();
14841            self.write_keyword("IF NOT EXISTS");
14842        }
14843
14844        self.write_space();
14845        self.generate_table(&ct.name)?;
14846
14847        if let TypeDefinition::Base {
14848            input,
14849            output,
14850            internallength,
14851        } = &ct.definition
14852        {
14853            if input.is_empty() && output.is_empty() && internallength.is_none() {
14854                return Ok(());
14855            }
14856        }
14857
14858        self.write_space();
14859        self.write_keyword("AS");
14860        self.write_space();
14861
14862        match &ct.definition {
14863            TypeDefinition::Enum(values) => {
14864                self.write_keyword("ENUM");
14865                self.write(" (");
14866                for (i, val) in values.iter().enumerate() {
14867                    if i > 0 {
14868                        self.write(", ");
14869                    }
14870                    self.write(&format!("'{}'", val));
14871                }
14872                self.write(")");
14873            }
14874            TypeDefinition::Composite(attrs) => {
14875                self.write("(");
14876                for (i, attr) in attrs.iter().enumerate() {
14877                    if i > 0 {
14878                        self.write(", ");
14879                    }
14880                    self.generate_identifier(&attr.name)?;
14881                    self.write_space();
14882                    self.generate_data_type(&attr.data_type)?;
14883                    if let Some(collate) = &attr.collate {
14884                        self.write_space();
14885                        self.write_keyword("COLLATE");
14886                        self.write_space();
14887                        self.generate_identifier(collate)?;
14888                    }
14889                }
14890                self.write(")");
14891            }
14892            TypeDefinition::Range {
14893                subtype,
14894                subtype_diff,
14895                canonical,
14896            } => {
14897                self.write_keyword("RANGE");
14898                self.write(" (");
14899                if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
14900                    self.write("subtype");
14901                } else {
14902                    self.write_keyword("SUBTYPE");
14903                }
14904                self.write(" = ");
14905                self.generate_data_type(subtype)?;
14906                if let Some(diff) = subtype_diff {
14907                    self.write(", ");
14908                    if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
14909                        self.write("subtype_diff");
14910                    } else {
14911                        self.write_keyword("SUBTYPE_DIFF");
14912                    }
14913                    self.write(" = ");
14914                    self.write(diff);
14915                }
14916                if let Some(canon) = canonical {
14917                    self.write(", ");
14918                    if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
14919                        self.write("canonical");
14920                    } else {
14921                        self.write_keyword("CANONICAL");
14922                    }
14923                    self.write(" = ");
14924                    self.write(canon);
14925                }
14926                self.write(")");
14927            }
14928            TypeDefinition::Base {
14929                input,
14930                output,
14931                internallength,
14932            } => {
14933                self.write("(");
14934                self.write_keyword("INPUT");
14935                self.write(" = ");
14936                self.write(input);
14937                self.write(", ");
14938                self.write_keyword("OUTPUT");
14939                self.write(" = ");
14940                self.write(output);
14941                if let Some(len) = internallength {
14942                    self.write(", ");
14943                    self.write_keyword("INTERNALLENGTH");
14944                    self.write(" = ");
14945                    self.write(&len.to_string());
14946                }
14947                self.write(")");
14948            }
14949            TypeDefinition::Domain {
14950                base_type,
14951                default,
14952                constraints,
14953            } => {
14954                self.generate_data_type(base_type)?;
14955                if let Some(def) = default {
14956                    self.write_space();
14957                    self.write_keyword("DEFAULT");
14958                    self.write_space();
14959                    self.generate_expression(def)?;
14960                }
14961                for constr in constraints {
14962                    self.write_space();
14963                    if let Some(name) = &constr.name {
14964                        self.write_keyword("CONSTRAINT");
14965                        self.write_space();
14966                        self.generate_identifier(name)?;
14967                        self.write_space();
14968                    }
14969                    self.write_keyword("CHECK");
14970                    self.write(" (");
14971                    self.generate_expression(&constr.check)?;
14972                    self.write(")");
14973                }
14974            }
14975        }
14976
14977        Ok(())
14978    }
14979
14980    fn generate_create_task(&mut self, task: &crate::expressions::CreateTask) -> Result<()> {
14981        self.write_keyword("CREATE");
14982        if task.or_replace {
14983            self.write_space();
14984            self.write_keyword("OR REPLACE");
14985        }
14986        self.write_space();
14987        self.write_keyword("TASK");
14988        if task.if_not_exists {
14989            self.write_space();
14990            self.write_keyword("IF NOT EXISTS");
14991        }
14992        self.write_space();
14993        self.write(&task.name);
14994        if !task.properties.is_empty() {
14995            // Properties already include leading whitespace from tokens_to_sql
14996            if !task.properties.starts_with('\n') && !task.properties.starts_with(' ') {
14997                self.write_space();
14998            }
14999            self.write(&task.properties);
15000        }
15001        self.write_space();
15002        self.write_keyword("AS");
15003        self.write_space();
15004        self.generate_expression(&task.body)?;
15005        Ok(())
15006    }
15007
15008    fn generate_try_catch(&mut self, try_catch: &TryCatch) -> Result<()> {
15009        self.write_keyword("BEGIN TRY");
15010        self.generate_tsql_block_statements(&try_catch.try_body)?;
15011        self.write_keyword("END TRY");
15012
15013        if let Some(catch_body) = &try_catch.catch_body {
15014            if self.config.pretty {
15015                self.write_newline();
15016                self.write_indent();
15017            } else {
15018                self.write_space();
15019            }
15020            self.write_keyword("BEGIN CATCH");
15021            self.generate_tsql_block_statements(catch_body)?;
15022            self.write_keyword("END CATCH");
15023        }
15024
15025        Ok(())
15026    }
15027
15028    fn generate_tsql_block_statements(&mut self, statements: &[Expression]) -> Result<()> {
15029        if statements.is_empty() {
15030            self.write_space();
15031            return Ok(());
15032        }
15033
15034        if self.config.pretty {
15035            self.indent_level += 1;
15036            for stmt in statements {
15037                self.write_newline();
15038                self.write_indent();
15039                self.generate_expression(stmt)?;
15040                self.write(";");
15041            }
15042            self.indent_level -= 1;
15043            self.write_newline();
15044            self.write_indent();
15045        } else {
15046            self.write_space();
15047            for (i, stmt) in statements.iter().enumerate() {
15048                if i > 0 {
15049                    self.write_space();
15050                }
15051                self.generate_expression(stmt)?;
15052                self.write(";");
15053            }
15054            self.write_space();
15055        }
15056
15057        Ok(())
15058    }
15059
15060    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
15061        self.write_keyword("DROP TYPE");
15062
15063        if dt.if_exists {
15064            self.write_space();
15065            self.write_keyword("IF EXISTS");
15066        }
15067
15068        self.write_space();
15069        self.generate_table(&dt.name)?;
15070
15071        if dt.cascade {
15072            self.write_space();
15073            self.write_keyword("CASCADE");
15074        }
15075
15076        Ok(())
15077    }
15078
15079    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
15080        // Athena: DESCRIBE uses Hive engine (backticks)
15081        let saved_athena_hive_context = self.athena_hive_context;
15082        if matches!(
15083            self.config.dialect,
15084            Some(crate::dialects::DialectType::Athena)
15085        ) {
15086            self.athena_hive_context = true;
15087        }
15088
15089        // Output leading comments before DESCRIBE
15090        for comment in &d.leading_comments {
15091            self.write_formatted_comment(comment);
15092            self.write(" ");
15093        }
15094
15095        self.write_keyword("DESCRIBE");
15096
15097        if d.extended {
15098            self.write_space();
15099            self.write_keyword("EXTENDED");
15100        } else if d.formatted {
15101            self.write_space();
15102            self.write_keyword("FORMATTED");
15103        }
15104
15105        // Output style like ANALYZE, HISTORY
15106        if let Some(ref style) = d.style {
15107            self.write_space();
15108            self.write_keyword(style);
15109        }
15110
15111        // Handle object kind (TABLE, VIEW) based on dialect
15112        let should_output_kind = match self.config.dialect {
15113            // Spark doesn't use TABLE/VIEW after DESCRIBE
15114            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
15115                false
15116            }
15117            // Snowflake always includes TABLE
15118            Some(DialectType::Snowflake) => true,
15119            _ => d.kind.is_some(),
15120        };
15121        if should_output_kind {
15122            if let Some(ref kind) = d.kind {
15123                self.write_space();
15124                self.write_keyword(kind);
15125            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
15126                self.write_space();
15127                self.write_keyword("TABLE");
15128            }
15129        }
15130
15131        self.write_space();
15132        self.generate_expression(&d.target)?;
15133
15134        // Output parenthesized parameter types for PROCEDURE/FUNCTION
15135        if !d.params.is_empty() {
15136            self.write("(");
15137            for (i, param) in d.params.iter().enumerate() {
15138                if i > 0 {
15139                    self.write(", ");
15140                }
15141                self.write(param);
15142            }
15143            self.write(")");
15144        }
15145
15146        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
15147        if let Some(ref partition) = d.partition {
15148            self.write_space();
15149            self.generate_expression(partition)?;
15150        }
15151
15152        // Databricks: AS JSON
15153        if d.as_json {
15154            self.write_space();
15155            self.write_keyword("AS JSON");
15156        }
15157
15158        // Output properties like type=stage
15159        for (name, value) in &d.properties {
15160            self.write_space();
15161            self.write(name);
15162            self.write("=");
15163            self.write(value);
15164        }
15165
15166        // Restore Athena Hive context
15167        self.athena_hive_context = saved_athena_hive_context;
15168
15169        Ok(())
15170    }
15171
15172    /// Generate SHOW statement (Snowflake, MySQL, etc.)
15173    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
15174    fn generate_show(&mut self, s: &Show) -> Result<()> {
15175        self.write_keyword("SHOW");
15176        self.write_space();
15177
15178        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
15179        // where TERSE is syntactically valid but has no effect on output
15180        let show_terse = s.terse
15181            && !matches!(
15182                s.this.as_str(),
15183                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
15184            );
15185        if show_terse {
15186            self.write_keyword("TERSE");
15187            self.write_space();
15188        }
15189
15190        // Object type (USERS, TABLES, DATABASES, etc.)
15191        self.write_keyword(&s.this);
15192
15193        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
15194        if let Some(ref target_expr) = s.target {
15195            self.write_space();
15196            self.generate_expression(target_expr)?;
15197        }
15198
15199        // HISTORY keyword
15200        if s.history {
15201            self.write_space();
15202            self.write_keyword("HISTORY");
15203        }
15204
15205        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
15206        if let Some(ref for_target) = s.for_target {
15207            self.write_space();
15208            self.write_keyword("FOR");
15209            self.write_space();
15210            self.generate_expression(for_target)?;
15211        }
15212
15213        // Determine ordering based on dialect:
15214        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
15215        // - MySQL: IN, FROM, LIKE (when FROM is present)
15216        use crate::dialects::DialectType;
15217        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
15218        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
15219        let mysql_tables_scope_as_from = is_mysql
15220            && matches!(s.this.as_str(), "TABLES" | "FULL TABLES")
15221            && s.scope_kind.as_deref() == Some("SCHEMA")
15222            && s.scope.is_some()
15223            && s.from.is_none();
15224
15225        if !is_snowflake && s.from.is_some() {
15226            // MySQL ordering: IN, FROM, LIKE
15227
15228            // IN scope_kind [scope]
15229            if let Some(ref scope_kind) = s.scope_kind {
15230                self.write_space();
15231                self.write_keyword("IN");
15232                self.write_space();
15233                self.write_keyword(scope_kind);
15234                if let Some(ref scope) = s.scope {
15235                    self.write_space();
15236                    self.generate_expression(scope)?;
15237                }
15238            } else if let Some(ref scope) = s.scope {
15239                self.write_space();
15240                self.write_keyword("IN");
15241                self.write_space();
15242                self.generate_expression(scope)?;
15243            }
15244
15245            // FROM clause
15246            if let Some(ref from) = s.from {
15247                self.write_space();
15248                self.write_keyword("FROM");
15249                self.write_space();
15250                self.generate_expression(from)?;
15251            }
15252
15253            // Second FROM clause (db name)
15254            if let Some(ref db) = s.db {
15255                self.write_space();
15256                self.write_keyword("FROM");
15257                self.write_space();
15258                self.generate_expression(db)?;
15259            }
15260
15261            // LIKE pattern
15262            if let Some(ref like) = s.like {
15263                self.write_space();
15264                self.write_keyword("LIKE");
15265                self.write_space();
15266                self.generate_expression(like)?;
15267            }
15268        } else {
15269            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
15270
15271            // LIKE pattern
15272            if let Some(ref like) = s.like {
15273                self.write_space();
15274                self.write_keyword("LIKE");
15275                self.write_space();
15276                self.generate_expression(like)?;
15277            }
15278
15279            // IN scope_kind [scope]
15280            if mysql_tables_scope_as_from {
15281                self.write_space();
15282                self.write_keyword("FROM");
15283                self.write_space();
15284                self.generate_expression(s.scope.as_ref().unwrap())?;
15285            } else if let Some(ref scope_kind) = s.scope_kind {
15286                self.write_space();
15287                self.write_keyword("IN");
15288                self.write_space();
15289                self.write_keyword(scope_kind);
15290                if let Some(ref scope) = s.scope {
15291                    self.write_space();
15292                    self.generate_expression(scope)?;
15293                }
15294            } else if let Some(ref scope) = s.scope {
15295                self.write_space();
15296                self.write_keyword("IN");
15297                self.write_space();
15298                self.generate_expression(scope)?;
15299            }
15300        }
15301
15302        // STARTS WITH pattern
15303        if let Some(ref starts_with) = s.starts_with {
15304            self.write_space();
15305            self.write_keyword("STARTS WITH");
15306            self.write_space();
15307            self.generate_expression(starts_with)?;
15308        }
15309
15310        // LIMIT clause
15311        if let Some(ref limit) = s.limit {
15312            self.write_space();
15313            self.generate_limit(limit)?;
15314        }
15315
15316        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
15317        if is_snowflake {
15318            if let Some(ref from) = s.from {
15319                self.write_space();
15320                self.write_keyword("FROM");
15321                self.write_space();
15322                self.generate_expression(from)?;
15323            }
15324        }
15325
15326        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
15327        if let Some(ref where_clause) = s.where_clause {
15328            self.write_space();
15329            self.write_keyword("WHERE");
15330            self.write_space();
15331            self.generate_expression(where_clause)?;
15332        }
15333
15334        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
15335        if let Some(is_mutex) = s.mutex {
15336            self.write_space();
15337            if is_mutex {
15338                self.write_keyword("MUTEX");
15339            } else {
15340                self.write_keyword("STATUS");
15341            }
15342        }
15343
15344        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
15345        if !s.privileges.is_empty() {
15346            self.write_space();
15347            self.write_keyword("WITH PRIVILEGES");
15348            self.write_space();
15349            for (i, priv_name) in s.privileges.iter().enumerate() {
15350                if i > 0 {
15351                    self.write(", ");
15352                }
15353                self.write_keyword(priv_name);
15354            }
15355        }
15356
15357        Ok(())
15358    }
15359
15360    // ==================== End DDL Generation ====================
15361
15362    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
15363        use crate::dialects::DialectType;
15364        match lit {
15365            Literal::String(s) => {
15366                self.generate_string_literal(s)?;
15367            }
15368            Literal::Number(n) => {
15369                if matches!(self.config.dialect, Some(DialectType::MySQL))
15370                    && n.len() > 2
15371                    && (n.starts_with("0x") || n.starts_with("0X"))
15372                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
15373                {
15374                    return self.generate_identifier(&Identifier {
15375                        name: n.clone(),
15376                        quoted: true,
15377                        trailing_comments: Vec::new(),
15378                        span: None,
15379                    });
15380                }
15381                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
15382                // for dialects that don't support them (MySQL interprets as identifier).
15383                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
15384                let n = if n.contains('_')
15385                    && !matches!(
15386                        self.config.dialect,
15387                        Some(DialectType::ClickHouse)
15388                            | Some(DialectType::DuckDB)
15389                            | Some(DialectType::PostgreSQL)
15390                            | Some(DialectType::Hive)
15391                            | Some(DialectType::Spark)
15392                            | Some(DialectType::Databricks)
15393                    ) {
15394                    std::borrow::Cow::Owned(n.replace('_', ""))
15395                } else {
15396                    std::borrow::Cow::Borrowed(n.as_str())
15397                };
15398                // Normalize numbers starting with decimal point to have leading zero
15399                // e.g., .25 -> 0.25 (matches sqlglot behavior)
15400                if n.starts_with('.') {
15401                    self.write("0");
15402                    self.write(&n);
15403                } else if n.starts_with("-.") {
15404                    // Handle negative numbers like -.25 -> -0.25
15405                    self.write("-0");
15406                    self.write(&n[1..]);
15407                } else {
15408                    self.write(&n);
15409                }
15410            }
15411            Literal::HexString(h) => {
15412                // Most dialects use lowercase x'...' for hex literals.
15413                match self.config.dialect {
15414                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
15415                        self.write("0x");
15416                        self.write(h);
15417                        return Ok(());
15418                    }
15419                    Some(DialectType::Spark)
15420                    | Some(DialectType::Databricks)
15421                    | Some(DialectType::Teradata) => self.write("X'"),
15422                    _ => self.write("x'"),
15423                }
15424                self.write(h);
15425                self.write("'");
15426            }
15427            Literal::HexNumber(h) => {
15428                // Hex number (0xA) - integer in hex notation (from BigQuery)
15429                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
15430                // For other dialects, convert to decimal integer
15431                match self.config.dialect {
15432                    Some(DialectType::BigQuery)
15433                    | Some(DialectType::ClickHouse)
15434                    | Some(DialectType::TSQL)
15435                    | Some(DialectType::Fabric) => {
15436                        self.write("0x");
15437                        self.write(h);
15438                    }
15439                    _ => {
15440                        // Convert hex to decimal
15441                        if let Ok(val) = u64::from_str_radix(h, 16) {
15442                            self.write(&val.to_string());
15443                        } else {
15444                            // Fallback: keep as 0x notation
15445                            self.write("0x");
15446                            self.write(h);
15447                        }
15448                    }
15449                }
15450            }
15451            Literal::BitString(b) => {
15452                // Bit string B'0101...'
15453                self.write("B'");
15454                self.write(b);
15455                self.write("'");
15456            }
15457            Literal::ByteString(b) => {
15458                // Byte string b'...' (BigQuery style)
15459                self.write("b'");
15460                // Escape special characters for output
15461                self.write_escaped_byte_string(b);
15462                self.write("'");
15463            }
15464            Literal::NationalString(s) => {
15465                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
15466                // Other dialects strip the N prefix and output as regular string
15467                let keep_n_prefix = matches!(
15468                    self.config.dialect,
15469                    Some(DialectType::TSQL)
15470                        | Some(DialectType::Oracle)
15471                        | Some(DialectType::MySQL)
15472                        | None
15473                );
15474                if keep_n_prefix {
15475                    self.write("N'");
15476                } else {
15477                    self.write("'");
15478                }
15479                self.write(s);
15480                self.write("'");
15481            }
15482            Literal::Date(d) => {
15483                self.generate_date_literal(d)?;
15484            }
15485            Literal::Time(t) => {
15486                self.generate_time_literal(t)?;
15487            }
15488            Literal::Timestamp(ts) => {
15489                self.generate_timestamp_literal(ts)?;
15490            }
15491            Literal::Datetime(dt) => {
15492                self.generate_datetime_literal(dt)?;
15493            }
15494            Literal::TripleQuotedString(s, _quote_char) => {
15495                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
15496                if matches!(
15497                    self.config.dialect,
15498                    Some(crate::dialects::DialectType::BigQuery)
15499                        | Some(crate::dialects::DialectType::DuckDB)
15500                        | Some(crate::dialects::DialectType::Snowflake)
15501                        | Some(crate::dialects::DialectType::Spark)
15502                        | Some(crate::dialects::DialectType::Hive)
15503                        | Some(crate::dialects::DialectType::Presto)
15504                        | Some(crate::dialects::DialectType::Trino)
15505                        | Some(crate::dialects::DialectType::PostgreSQL)
15506                        | Some(crate::dialects::DialectType::MySQL)
15507                        | Some(crate::dialects::DialectType::Redshift)
15508                        | Some(crate::dialects::DialectType::TSQL)
15509                        | Some(crate::dialects::DialectType::Oracle)
15510                        | Some(crate::dialects::DialectType::ClickHouse)
15511                        | Some(crate::dialects::DialectType::Databricks)
15512                        | Some(crate::dialects::DialectType::SQLite)
15513                ) {
15514                    self.generate_string_literal(s)?;
15515                } else {
15516                    // Preserve triple-quoted string syntax for generic/unknown dialects
15517                    let quotes = format!("{0}{0}{0}", _quote_char);
15518                    self.write(&quotes);
15519                    self.write(s);
15520                    self.write(&quotes);
15521                }
15522            }
15523            Literal::EscapeString(s) => {
15524                // PostgreSQL escape string: e'...' or E'...'
15525                // Token text format is "e:content" or "E:content"
15526                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
15527                use crate::dialects::DialectType;
15528                let content = if let Some(c) = s.strip_prefix("e:") {
15529                    c
15530                } else if let Some(c) = s.strip_prefix("E:") {
15531                    c
15532                } else {
15533                    s.as_str()
15534                };
15535
15536                // MySQL strips the PostgreSQL E prefix but still emits a string literal.
15537                if matches!(
15538                    self.config.dialect,
15539                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
15540                ) {
15541                    self.write("'");
15542                    self.write(&content.replace('\'', "''"));
15543                    self.write("'");
15544                } else {
15545                    // Some dialects use lowercase e' prefix
15546                    let prefix = if matches!(
15547                        self.config.dialect,
15548                        Some(DialectType::SingleStore)
15549                            | Some(DialectType::DuckDB)
15550                            | Some(DialectType::PostgreSQL)
15551                            | Some(DialectType::CockroachDB)
15552                            | Some(DialectType::Materialize)
15553                            | Some(DialectType::RisingWave)
15554                    ) {
15555                        "e'"
15556                    } else {
15557                        "E'"
15558                    };
15559
15560                    // Normalize \' to '' for output
15561                    let normalized = content.replace("\\'", "''");
15562                    self.write(prefix);
15563                    self.write(&normalized);
15564                    self.write("'");
15565                }
15566            }
15567            Literal::DollarString(s) => {
15568                // Convert dollar-quoted strings to single-quoted strings
15569                // (like Python sqlglot's rawstring_sql)
15570                use crate::dialects::DialectType;
15571                // Extract content from tag\x00content format
15572                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
15573                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
15574                let escape_backslash = matches!(
15575                    self.config.dialect,
15576                    Some(DialectType::ClickHouse) | Some(DialectType::Snowflake)
15577                );
15578                // Step 2: Determine quote escaping style
15579                // Snowflake: ' -> \' (backslash escape)
15580                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
15581                let use_backslash_quote =
15582                    matches!(self.config.dialect, Some(DialectType::Snowflake));
15583
15584                let mut escaped = String::with_capacity(content.len() + 4);
15585                for ch in content.chars() {
15586                    if escape_backslash && ch == '\\' {
15587                        // Escape backslash first (before quote escaping)
15588                        escaped.push('\\');
15589                        escaped.push('\\');
15590                    } else if ch == '\'' {
15591                        if use_backslash_quote {
15592                            escaped.push('\\');
15593                            escaped.push('\'');
15594                        } else {
15595                            escaped.push('\'');
15596                            escaped.push('\'');
15597                        }
15598                    } else {
15599                        escaped.push(ch);
15600                    }
15601                }
15602                self.write("'");
15603                self.write(&escaped);
15604                self.write("'");
15605            }
15606            Literal::RawString(s) => {
15607                // Raw strings (r"..." or r'...') contain literal backslashes.
15608                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
15609                // 1. If \\ is in STRING_ESCAPES, double all backslashes
15610                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
15611                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
15612                use crate::dialects::DialectType;
15613
15614                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
15615                let escape_backslash = matches!(
15616                    self.config.dialect,
15617                    Some(DialectType::BigQuery)
15618                        | Some(DialectType::MySQL)
15619                        | Some(DialectType::SingleStore)
15620                        | Some(DialectType::TiDB)
15621                        | Some(DialectType::Hive)
15622                        | Some(DialectType::Spark)
15623                        | Some(DialectType::Databricks)
15624                        | Some(DialectType::Drill)
15625                        | Some(DialectType::Snowflake)
15626                        | Some(DialectType::Redshift)
15627                        | Some(DialectType::ClickHouse)
15628                );
15629
15630                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
15631                // These escape quotes as \' instead of ''
15632                let backslash_escapes_quote = matches!(
15633                    self.config.dialect,
15634                    Some(DialectType::BigQuery)
15635                        | Some(DialectType::Hive)
15636                        | Some(DialectType::Spark)
15637                        | Some(DialectType::Databricks)
15638                        | Some(DialectType::Drill)
15639                        | Some(DialectType::Snowflake)
15640                        | Some(DialectType::Redshift)
15641                );
15642
15643                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
15644                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
15645                let supports_escape_sequences = escape_backslash;
15646
15647                let mut escaped = String::with_capacity(s.len() + 4);
15648                for ch in s.chars() {
15649                    if escape_backslash && ch == '\\' {
15650                        // Double the backslash for the target dialect
15651                        escaped.push('\\');
15652                        escaped.push('\\');
15653                    } else if ch == '\'' {
15654                        if backslash_escapes_quote {
15655                            // Use backslash to escape the quote: \'
15656                            escaped.push('\\');
15657                            escaped.push('\'');
15658                        } else {
15659                            // Use SQL standard quote doubling: ''
15660                            escaped.push('\'');
15661                            escaped.push('\'');
15662                        }
15663                    } else if supports_escape_sequences {
15664                        // Apply ESCAPED_SEQUENCES mapping for special chars
15665                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
15666                        match ch {
15667                            '\n' => {
15668                                escaped.push('\\');
15669                                escaped.push('n');
15670                            }
15671                            '\r' => {
15672                                escaped.push('\\');
15673                                escaped.push('r');
15674                            }
15675                            '\t' => {
15676                                escaped.push('\\');
15677                                escaped.push('t');
15678                            }
15679                            '\x07' => {
15680                                escaped.push('\\');
15681                                escaped.push('a');
15682                            }
15683                            '\x08' => {
15684                                escaped.push('\\');
15685                                escaped.push('b');
15686                            }
15687                            '\x0C' => {
15688                                escaped.push('\\');
15689                                escaped.push('f');
15690                            }
15691                            '\x0B' => {
15692                                escaped.push('\\');
15693                                escaped.push('v');
15694                            }
15695                            _ => escaped.push(ch),
15696                        }
15697                    } else {
15698                        escaped.push(ch);
15699                    }
15700                }
15701                self.write("'");
15702                self.write(&escaped);
15703                self.write("'");
15704            }
15705        }
15706        Ok(())
15707    }
15708
15709    /// Generate a DATE literal with dialect-specific formatting
15710    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
15711        use crate::dialects::DialectType;
15712
15713        match self.config.dialect {
15714            // SQL Server / Fabric use CONVERT or CAST
15715            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
15716                self.write("CAST('");
15717                self.write(d);
15718                self.write("' AS DATE)");
15719            }
15720            // BigQuery uses CAST syntax for type literals
15721            // DATE 'value' -> CAST('value' AS DATE)
15722            Some(DialectType::BigQuery) => {
15723                self.write("CAST('");
15724                self.write(d);
15725                self.write("' AS DATE)");
15726            }
15727            // Exasol uses CAST syntax for DATE literals
15728            // DATE 'value' -> CAST('value' AS DATE)
15729            Some(DialectType::Exasol) => {
15730                self.write("CAST('");
15731                self.write(d);
15732                self.write("' AS DATE)");
15733            }
15734            // Snowflake uses CAST syntax for DATE literals
15735            // DATE 'value' -> CAST('value' AS DATE)
15736            Some(DialectType::Snowflake) => {
15737                self.write("CAST('");
15738                self.write(d);
15739                self.write("' AS DATE)");
15740            }
15741            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
15742            Some(DialectType::PostgreSQL)
15743            | Some(DialectType::MySQL)
15744            | Some(DialectType::SingleStore)
15745            | Some(DialectType::TiDB)
15746            | Some(DialectType::Redshift) => {
15747                self.write("CAST('");
15748                self.write(d);
15749                self.write("' AS DATE)");
15750            }
15751            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
15752            Some(DialectType::DuckDB)
15753            | Some(DialectType::Presto)
15754            | Some(DialectType::Trino)
15755            | Some(DialectType::Athena)
15756            | Some(DialectType::Spark)
15757            | Some(DialectType::Databricks)
15758            | Some(DialectType::Hive) => {
15759                self.write("CAST('");
15760                self.write(d);
15761                self.write("' AS DATE)");
15762            }
15763            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
15764            Some(DialectType::Oracle) => {
15765                self.write("TO_DATE('");
15766                self.write(d);
15767                self.write("', 'YYYY-MM-DD')");
15768            }
15769            // Standard SQL: DATE '...'
15770            _ => {
15771                self.write_keyword("DATE");
15772                self.write(" '");
15773                self.write(d);
15774                self.write("'");
15775            }
15776        }
15777        Ok(())
15778    }
15779
15780    /// Generate a TIME literal with dialect-specific formatting
15781    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
15782        use crate::dialects::DialectType;
15783
15784        match self.config.dialect {
15785            // SQL Server uses CONVERT or CAST
15786            Some(DialectType::TSQL) => {
15787                self.write("CAST('");
15788                self.write(t);
15789                self.write("' AS TIME)");
15790            }
15791            // Standard SQL: TIME '...'
15792            _ => {
15793                self.write_keyword("TIME");
15794                self.write(" '");
15795                self.write(t);
15796                self.write("'");
15797            }
15798        }
15799        Ok(())
15800    }
15801
15802    /// Generate a date expression for Dremio, converting DATE literals to CAST
15803    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
15804        use crate::expressions::Literal;
15805
15806        match expr {
15807            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
15808                let Literal::Date(d) = lit.as_ref() else {
15809                    unreachable!()
15810                };
15811                // DATE 'value' -> CAST('value' AS DATE)
15812                self.write("CAST('");
15813                self.write(d);
15814                self.write("' AS DATE)");
15815            }
15816            _ => {
15817                // For all other expressions, generate normally
15818                self.generate_expression(expr)?;
15819            }
15820        }
15821        Ok(())
15822    }
15823
15824    /// Generate a TIMESTAMP literal with dialect-specific formatting
15825    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
15826        use crate::dialects::DialectType;
15827
15828        match self.config.dialect {
15829            // SQL Server uses CONVERT or CAST
15830            Some(DialectType::TSQL) => {
15831                self.write("CAST('");
15832                self.write(ts);
15833                self.write("' AS DATETIME2)");
15834            }
15835            // BigQuery uses CAST syntax for type literals
15836            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15837            Some(DialectType::BigQuery) => {
15838                self.write("CAST('");
15839                self.write(ts);
15840                self.write("' AS TIMESTAMP)");
15841            }
15842            // Snowflake uses CAST syntax for TIMESTAMP literals
15843            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15844            Some(DialectType::Snowflake) => {
15845                self.write("CAST('");
15846                self.write(ts);
15847                self.write("' AS TIMESTAMP)");
15848            }
15849            // Dremio uses CAST syntax for TIMESTAMP literals
15850            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15851            Some(DialectType::Dremio) => {
15852                self.write("CAST('");
15853                self.write(ts);
15854                self.write("' AS TIMESTAMP)");
15855            }
15856            // Exasol uses CAST syntax for TIMESTAMP literals
15857            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15858            Some(DialectType::Exasol) => {
15859                self.write("CAST('");
15860                self.write(ts);
15861                self.write("' AS TIMESTAMP)");
15862            }
15863            // Oracle prefers TO_TIMESTAMP function call
15864            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
15865            Some(DialectType::Oracle) => {
15866                self.write("TO_TIMESTAMP('");
15867                self.write(ts);
15868                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
15869            }
15870            // Presto/Trino: always use CAST for TIMESTAMP literals
15871            Some(DialectType::Presto) | Some(DialectType::Trino) => {
15872                if Self::timestamp_has_timezone(ts) {
15873                    self.write("CAST('");
15874                    self.write(ts);
15875                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
15876                } else {
15877                    self.write("CAST('");
15878                    self.write(ts);
15879                    self.write("' AS TIMESTAMP)");
15880                }
15881            }
15882            // ClickHouse: CAST('...' AS Nullable(DateTime))
15883            Some(DialectType::ClickHouse) => {
15884                self.write("CAST('");
15885                self.write(ts);
15886                self.write("' AS Nullable(DateTime))");
15887            }
15888            // Spark: CAST('...' AS TIMESTAMP)
15889            Some(DialectType::Spark) => {
15890                self.write("CAST('");
15891                self.write(ts);
15892                self.write("' AS TIMESTAMP)");
15893            }
15894            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
15895            // but TIMESTAMP '...' for special values like 'epoch'
15896            Some(DialectType::Redshift) => {
15897                if ts == "epoch" {
15898                    self.write_keyword("TIMESTAMP");
15899                    self.write(" '");
15900                    self.write(ts);
15901                    self.write("'");
15902                } else {
15903                    self.write("CAST('");
15904                    self.write(ts);
15905                    self.write("' AS TIMESTAMP)");
15906                }
15907            }
15908            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
15909            Some(DialectType::PostgreSQL)
15910            | Some(DialectType::Hive)
15911            | Some(DialectType::SQLite)
15912            | Some(DialectType::DuckDB)
15913            | Some(DialectType::Athena)
15914            | Some(DialectType::Drill)
15915            | Some(DialectType::Teradata) => {
15916                self.write("CAST('");
15917                self.write(ts);
15918                self.write("' AS TIMESTAMP)");
15919            }
15920            // MySQL/StarRocks: CAST('...' AS DATETIME)
15921            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
15922                self.write("CAST('");
15923                self.write(ts);
15924                self.write("' AS DATETIME)");
15925            }
15926            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
15927            Some(DialectType::Databricks) => {
15928                self.write("CAST('");
15929                self.write(ts);
15930                self.write("' AS TIMESTAMP_NTZ)");
15931            }
15932            // Standard SQL: TIMESTAMP '...'
15933            _ => {
15934                self.write_keyword("TIMESTAMP");
15935                self.write(" '");
15936                self.write(ts);
15937                self.write("'");
15938            }
15939        }
15940        Ok(())
15941    }
15942
15943    /// Check if a timestamp string contains a timezone identifier
15944    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
15945    fn timestamp_has_timezone(ts: &str) -> bool {
15946        // Check for common IANA timezone patterns: Continent/City format
15947        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
15948        // Also handles: UTC, GMT, Etc/GMT+0, etc.
15949        let ts_lower = ts.to_ascii_lowercase();
15950
15951        // Check for Continent/City pattern (most common)
15952        let continent_prefixes = [
15953            "africa/",
15954            "america/",
15955            "antarctica/",
15956            "arctic/",
15957            "asia/",
15958            "atlantic/",
15959            "australia/",
15960            "europe/",
15961            "indian/",
15962            "pacific/",
15963            "etc/",
15964            "brazil/",
15965            "canada/",
15966            "chile/",
15967            "mexico/",
15968            "us/",
15969        ];
15970
15971        for prefix in &continent_prefixes {
15972            if ts_lower.contains(prefix) {
15973                return true;
15974            }
15975        }
15976
15977        // Check for standalone timezone abbreviations at the end
15978        // These typically appear after the time portion
15979        let tz_abbrevs = [
15980            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
15981            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
15982            " sgt", " aest", " aedt", " acst", " acdt", " awst",
15983        ];
15984
15985        for abbrev in &tz_abbrevs {
15986            if ts_lower.ends_with(abbrev) {
15987                return true;
15988            }
15989        }
15990
15991        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
15992        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
15993        // Look for pattern: space followed by + or - and digits (optionally with :)
15994        let trimmed = ts.trim();
15995        if let Some(last_space) = trimmed.rfind(' ') {
15996            let suffix = &trimmed[last_space + 1..];
15997            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
15998                // Check if rest is numeric (possibly with : for hh:mm format)
15999                let rest = &suffix[1..];
16000                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
16001                    return true;
16002                }
16003            }
16004        }
16005
16006        false
16007    }
16008
16009    /// Generate a DATETIME literal with dialect-specific formatting
16010    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
16011        use crate::dialects::DialectType;
16012
16013        match self.config.dialect {
16014            // BigQuery uses CAST syntax for type literals
16015            // DATETIME 'value' -> CAST('value' AS DATETIME)
16016            Some(DialectType::BigQuery) => {
16017                self.write("CAST('");
16018                self.write(dt);
16019                self.write("' AS DATETIME)");
16020            }
16021            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
16022            Some(DialectType::DuckDB) => {
16023                self.write("CAST('");
16024                self.write(dt);
16025                self.write("' AS TIMESTAMP)");
16026            }
16027            // DATETIME is primarily a BigQuery type
16028            // Output as DATETIME '...' for dialects that support it
16029            _ => {
16030                self.write_keyword("DATETIME");
16031                self.write(" '");
16032                self.write(dt);
16033                self.write("'");
16034            }
16035        }
16036        Ok(())
16037    }
16038
16039    /// Generate a string literal with dialect-specific escaping
16040    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
16041        use crate::dialects::DialectType;
16042
16043        match self.config.dialect {
16044            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
16045            // and backslash escaping for special characters like newlines
16046            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
16047            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
16048                // Hive/Spark use backslash escaping for quotes (\') and special chars
16049                self.write("'");
16050                for c in s.chars() {
16051                    match c {
16052                        '\'' => self.write("\\'"),
16053                        '\\' => self.write("\\\\"),
16054                        '\n' => self.write("\\n"),
16055                        '\r' => self.write("\\r"),
16056                        '\t' => self.write("\\t"),
16057                        '\0' => self.write("\\0"),
16058                        _ => self.output.push(c),
16059                    }
16060                }
16061                self.write("'");
16062            }
16063            Some(DialectType::Drill) => {
16064                // Drill uses SQL-standard quote doubling ('') for quotes,
16065                // but backslash escaping for special characters
16066                self.write("'");
16067                for c in s.chars() {
16068                    match c {
16069                        '\'' => self.write("''"),
16070                        '\\' => self.write("\\\\"),
16071                        '\n' => self.write("\\n"),
16072                        '\r' => self.write("\\r"),
16073                        '\t' => self.write("\\t"),
16074                        '\0' => self.write("\\0"),
16075                        _ => self.output.push(c),
16076                    }
16077                }
16078                self.write("'");
16079            }
16080            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
16081                self.write("'");
16082                for c in s.chars() {
16083                    match c {
16084                        // MySQL uses SQL standard quote doubling
16085                        '\'' => self.write("''"),
16086                        '\\' => self.write("\\\\"),
16087                        '\n' => self.write("\\n"),
16088                        '\r' => self.write("\\r"),
16089                        '\t' => self.write("\\t"),
16090                        // sqlglot writes a literal NUL for this case
16091                        '\0' => self.output.push('\0'),
16092                        _ => self.output.push(c),
16093                    }
16094                }
16095                self.write("'");
16096            }
16097            // BigQuery: Uses backslash escaping
16098            Some(DialectType::BigQuery) => {
16099                self.write("'");
16100                for c in s.chars() {
16101                    match c {
16102                        '\'' => self.write("\\'"),
16103                        '\\' => self.write("\\\\"),
16104                        '\n' => self.write("\\n"),
16105                        '\r' => self.write("\\r"),
16106                        '\t' => self.write("\\t"),
16107                        '\0' => self.write("\\0"),
16108                        '\x07' => self.write("\\a"),
16109                        '\x08' => self.write("\\b"),
16110                        '\x0C' => self.write("\\f"),
16111                        '\x0B' => self.write("\\v"),
16112                        _ => self.output.push(c),
16113                    }
16114                }
16115                self.write("'");
16116            }
16117            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
16118            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
16119            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
16120            Some(DialectType::Athena) => {
16121                if self.athena_hive_context {
16122                    // Hive-style: backslash escaping
16123                    self.write("'");
16124                    for c in s.chars() {
16125                        match c {
16126                            '\'' => self.write("\\'"),
16127                            '\\' => self.write("\\\\"),
16128                            '\n' => self.write("\\n"),
16129                            '\r' => self.write("\\r"),
16130                            '\t' => self.write("\\t"),
16131                            '\0' => self.write("\\0"),
16132                            _ => self.output.push(c),
16133                        }
16134                    }
16135                    self.write("'");
16136                } else {
16137                    // Trino-style: SQL-standard escaping, preserve backslashes
16138                    self.write("'");
16139                    for c in s.chars() {
16140                        match c {
16141                            '\'' => self.write("''"),
16142                            // Preserve backslashes literally (no re-escaping)
16143                            _ => self.output.push(c),
16144                        }
16145                    }
16146                    self.write("'");
16147                }
16148            }
16149            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
16150            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
16151            // becomes string value '\\'), so we should NOT re-escape backslashes.
16152            // We only need to escape single quotes.
16153            Some(DialectType::Snowflake) => {
16154                self.write("'");
16155                for c in s.chars() {
16156                    match c {
16157                        '\'' => self.write("\\'"),
16158                        // Backslashes are already escaped in the tokenized string, don't re-escape
16159                        // Only escape special characters that might not have been escaped
16160                        '\n' => self.write("\\n"),
16161                        '\r' => self.write("\\r"),
16162                        '\t' => self.write("\\t"),
16163                        _ => self.output.push(c),
16164                    }
16165                }
16166                self.write("'");
16167            }
16168            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
16169            Some(DialectType::PostgreSQL) => {
16170                self.write("'");
16171                for c in s.chars() {
16172                    match c {
16173                        '\'' => self.write("''"),
16174                        _ => self.output.push(c),
16175                    }
16176                }
16177                self.write("'");
16178            }
16179            // Redshift: Uses backslash escaping for single quotes
16180            Some(DialectType::Redshift) => {
16181                self.write("'");
16182                for c in s.chars() {
16183                    match c {
16184                        '\'' => self.write("\\'"),
16185                        _ => self.output.push(c),
16186                    }
16187                }
16188                self.write("'");
16189            }
16190            // Oracle: Uses standard double single-quote escaping
16191            Some(DialectType::Oracle) => {
16192                self.write("'");
16193                for ch in s.chars() {
16194                    if ch == '\'' {
16195                        self.output.push_str("''");
16196                    } else {
16197                        self.output.push(ch);
16198                    }
16199                }
16200                self.write("'");
16201            }
16202            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
16203            // backslash escaping for backslashes and special characters
16204            Some(DialectType::ClickHouse) => {
16205                self.write("'");
16206                for c in s.chars() {
16207                    match c {
16208                        '\'' => self.write("''"),
16209                        '\\' => self.write("\\\\"),
16210                        '\n' => self.write("\\n"),
16211                        '\r' => self.write("\\r"),
16212                        '\t' => self.write("\\t"),
16213                        '\0' => self.write("\\0"),
16214                        '\x07' => self.write("\\a"),
16215                        '\x08' => self.write("\\b"),
16216                        '\x0C' => self.write("\\f"),
16217                        '\x0B' => self.write("\\v"),
16218                        // Non-printable characters: emit as \xNN hex escapes
16219                        c if c.is_control() || (c as u32) < 0x20 => {
16220                            let byte = c as u32;
16221                            if byte < 256 {
16222                                self.write(&format!("\\x{:02X}", byte));
16223                            } else {
16224                                self.output.push(c);
16225                            }
16226                        }
16227                        _ => self.output.push(c),
16228                    }
16229                }
16230                self.write("'");
16231            }
16232            // Default: SQL standard double single quotes (works for most dialects)
16233            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
16234            _ => {
16235                self.write("'");
16236                for ch in s.chars() {
16237                    if ch == '\'' {
16238                        self.output.push_str("''");
16239                    } else {
16240                        self.output.push(ch);
16241                    }
16242                }
16243                self.write("'");
16244            }
16245        }
16246        Ok(())
16247    }
16248
16249    /// Write a byte string with proper escaping for BigQuery-style byte literals
16250    /// Escapes characters as \xNN hex escapes where needed
16251    fn write_escaped_byte_string(&mut self, s: &str) {
16252        for c in s.chars() {
16253            match c {
16254                // Escape single quotes
16255                '\'' => self.write("\\'"),
16256                // Escape backslashes
16257                '\\' => self.write("\\\\"),
16258                // Keep all printable characters (including non-ASCII) as-is
16259                _ if !c.is_control() => self.output.push(c),
16260                // Escape control characters as hex
16261                _ => {
16262                    let byte = c as u32;
16263                    if byte < 256 {
16264                        self.write(&format!("\\x{:02x}", byte));
16265                    } else {
16266                        // For unicode characters, write each UTF-8 byte
16267                        for b in c.to_string().as_bytes() {
16268                            self.write(&format!("\\x{:02x}", b));
16269                        }
16270                    }
16271                }
16272            }
16273        }
16274    }
16275
16276    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
16277        use crate::dialects::DialectType;
16278
16279        // Different dialects have different boolean literal formats
16280        match self.config.dialect {
16281            // SQL Server typically uses 1/0 for boolean literals in many contexts
16282            // However, TRUE/FALSE also works in modern versions
16283            Some(DialectType::TSQL) => {
16284                self.write(if b.value { "1" } else { "0" });
16285            }
16286            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
16287            Some(DialectType::Oracle) => {
16288                self.write(if b.value { "1" } else { "0" });
16289            }
16290            // MySQL accepts TRUE/FALSE as aliases for 1/0
16291            Some(DialectType::MySQL) => {
16292                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
16293            }
16294            // Most other dialects support TRUE/FALSE
16295            _ => {
16296                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
16297            }
16298        }
16299        Ok(())
16300    }
16301
16302    /// Generate an identifier that's used as an alias name
16303    /// This quotes reserved keywords in addition to already-quoted identifiers
16304    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
16305        let name = &id.name;
16306        let quote_style = &self.config.identifier_quote_style;
16307
16308        // For aliases, quote if:
16309        // 1. The identifier was explicitly quoted in the source
16310        // 2. The identifier is a reserved keyword for the current dialect
16311        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
16312
16313        // Normalize identifier if configured
16314        let output_name = if self.config.normalize_identifiers && !id.quoted {
16315            name.to_ascii_lowercase()
16316        } else {
16317            name.to_string()
16318        };
16319
16320        if needs_quoting {
16321            let quote_style = if matches!(self.config.dialect, Some(DialectType::ClickHouse))
16322                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
16323                && quote_style.start == '"'
16324                && output_name.contains('"')
16325            {
16326                &IdentifierQuoteStyle::BACKTICK
16327            } else {
16328                quote_style
16329            };
16330            // Escape any quote characters within the identifier
16331            let escaped_name = if quote_style.start == quote_style.end {
16332                output_name.replace(
16333                    quote_style.end,
16334                    &format!("{}{}", quote_style.end, quote_style.end),
16335                )
16336            } else {
16337                output_name.replace(
16338                    quote_style.end,
16339                    &format!("{}{}", quote_style.end, quote_style.end),
16340                )
16341            };
16342            self.write(&format!(
16343                "{}{}{}",
16344                quote_style.start, escaped_name, quote_style.end
16345            ));
16346        } else {
16347            self.write(&output_name);
16348        }
16349
16350        // Output trailing comments
16351        for comment in &id.trailing_comments {
16352            self.write(" ");
16353            self.write_formatted_comment(comment);
16354        }
16355        Ok(())
16356    }
16357
16358    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
16359        use crate::dialects::DialectType;
16360
16361        let name = &id.name;
16362
16363        // For Athena, use backticks in Hive context, double quotes in Trino context
16364        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
16365            && self.athena_hive_context
16366        {
16367            &IdentifierQuoteStyle::BACKTICK
16368        } else {
16369            &self.config.identifier_quote_style
16370        };
16371
16372        // Quote if:
16373        // 1. The identifier was explicitly quoted in the source
16374        // 2. The identifier is a reserved keyword for the current dialect
16375        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
16376        // This matches Python sqlglot's identifier_sql behavior
16377        // Also quote identifiers starting with digits if the target dialect doesn't support them
16378        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
16379        let needs_digit_quoting = starts_with_digit
16380            && !self.config.identifiers_can_start_with_digit
16381            && self.config.dialect.is_some();
16382        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
16383            && name.len() > 2
16384            && (name.starts_with("0x") || name.starts_with("0X"))
16385            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
16386        let clickhouse_unsafe_identifier =
16387            matches!(self.config.dialect, Some(DialectType::ClickHouse))
16388                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
16389                && !name.starts_with('{')
16390                && !name.contains('(')
16391                && !name.contains(')')
16392                && name != "?"
16393                && name
16394                    .chars()
16395                    .any(|c| !(c.is_ascii_alphanumeric() || c == '_'));
16396        let needs_quoting = id.quoted
16397            || self.is_reserved_keyword(name)
16398            || self.config.always_quote_identifiers
16399            || needs_digit_quoting
16400            || mysql_invalid_hex_identifier
16401            || clickhouse_unsafe_identifier;
16402
16403        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
16404        // When quoted, we need to output `name`(16) not `name(16)`
16405        let (base_name, suffix) = if needs_quoting {
16406            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
16407            if let Some(paren_pos) = name.find('(') {
16408                let base = &name[..paren_pos];
16409                let rest = &name[paren_pos..];
16410                // Verify it looks like (digits) or (digits) ASC/DESC
16411                if rest.starts_with('(')
16412                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
16413                {
16414                    // Check if content between parens is all digits
16415                    let close_paren = rest.find(')').unwrap_or(rest.len());
16416                    let inside = &rest[1..close_paren];
16417                    if inside.chars().all(|c| c.is_ascii_digit()) {
16418                        (base.to_string(), rest.to_string())
16419                    } else {
16420                        (name.to_string(), String::new())
16421                    }
16422                } else {
16423                    (name.to_string(), String::new())
16424                }
16425            } else if name.ends_with(" ASC") {
16426                let base = &name[..name.len() - 4];
16427                (base.to_string(), " ASC".to_string())
16428            } else if name.ends_with(" DESC") {
16429                let base = &name[..name.len() - 5];
16430                (base.to_string(), " DESC".to_string())
16431            } else {
16432                (name.to_string(), String::new())
16433            }
16434        } else {
16435            (name.to_string(), String::new())
16436        };
16437
16438        // Normalize identifier if configured, with special handling for Exasol
16439        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
16440        // should be uppercased when not already quoted (to match Python sqlglot behavior)
16441        let output_name = if self.config.normalize_identifiers && !id.quoted {
16442            base_name.to_ascii_lowercase()
16443        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
16444            && !id.quoted
16445            && self.is_reserved_keyword(name)
16446        {
16447            // Exasol: uppercase reserved keywords when quoting them
16448            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
16449            base_name.to_ascii_uppercase()
16450        } else {
16451            base_name
16452        };
16453
16454        if needs_quoting {
16455            // Escape any quote characters within the identifier
16456            let escaped_name = if quote_style.start == quote_style.end {
16457                // Same start/end char (e.g., " or `) - double the quote char
16458                output_name.replace(
16459                    quote_style.end,
16460                    &format!("{}{}", quote_style.end, quote_style.end),
16461                )
16462            } else {
16463                // Different start/end (e.g., [ and ]) - escape only the end char
16464                output_name.replace(
16465                    quote_style.end,
16466                    &format!("{}{}", quote_style.end, quote_style.end),
16467                )
16468            };
16469            self.write(&format!(
16470                "{}{}{}{}",
16471                quote_style.start, escaped_name, quote_style.end, suffix
16472            ));
16473        } else {
16474            self.write(&output_name);
16475        }
16476
16477        // Output trailing comments
16478        for comment in &id.trailing_comments {
16479            self.write(" ");
16480            self.write_formatted_comment(comment);
16481        }
16482        Ok(())
16483    }
16484
16485    fn generate_column(&mut self, col: &Column) -> Result<()> {
16486        use crate::dialects::DialectType;
16487
16488        if let Some(table) = &col.table {
16489            // Exasol special case: LOCAL as column table prefix should NOT be quoted
16490            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
16491            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
16492            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
16493                && !table.quoted
16494                && table.name.eq_ignore_ascii_case("LOCAL");
16495
16496            if is_exasol_local_prefix {
16497                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
16498                self.write("LOCAL");
16499            } else {
16500                self.generate_identifier(table)?;
16501            }
16502            self.write(".");
16503        }
16504        self.generate_identifier(&col.name)?;
16505        // Oracle-style join marker (+)
16506        // Only output if dialect supports it (Oracle, Exasol)
16507        if col.join_mark && self.config.supports_column_join_marks {
16508            self.write(" (+)");
16509        }
16510        // Output trailing comments
16511        for comment in &col.trailing_comments {
16512            self.write_space();
16513            self.write_formatted_comment(comment);
16514        }
16515        Ok(())
16516    }
16517
16518    fn generate_prepare(&mut self, prepare: &PrepareStatement) -> Result<()> {
16519        self.write_keyword("PREPARE");
16520        self.write_space();
16521        self.generate_identifier(&prepare.name)?;
16522
16523        if !prepare.parameter_types.is_empty() {
16524            self.write(" (");
16525            for (i, data_type) in prepare.parameter_types.iter().enumerate() {
16526                if i > 0 {
16527                    self.write(", ");
16528                }
16529                self.generate_data_type(data_type)?;
16530            }
16531            self.write(")");
16532        }
16533
16534        self.write_space();
16535        self.write_keyword("AS");
16536        self.write_space();
16537        self.generate_expression(&prepare.statement)
16538    }
16539
16540    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
16541    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
16542    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
16543        use crate::dialects::DialectType;
16544        use crate::expressions::PseudocolumnType;
16545
16546        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
16547        if pc.kind == PseudocolumnType::Sysdate
16548            && !matches!(
16549                self.config.dialect,
16550                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
16551            )
16552        {
16553            self.write_keyword("CURRENT_TIMESTAMP");
16554            // Add () for dialects that expect it
16555            if matches!(
16556                self.config.dialect,
16557                Some(DialectType::MySQL)
16558                    | Some(DialectType::ClickHouse)
16559                    | Some(DialectType::Spark)
16560                    | Some(DialectType::Databricks)
16561                    | Some(DialectType::Hive)
16562            ) {
16563                self.write("()");
16564            }
16565        } else {
16566            self.write(pc.kind.as_str());
16567        }
16568        Ok(())
16569    }
16570
16571    /// Generate CONNECT BY clause (Oracle hierarchical queries)
16572    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
16573        use crate::dialects::DialectType;
16574
16575        // Generate native CONNECT BY for Oracle and Snowflake
16576        // For other dialects, add a comment noting manual conversion needed
16577        let supports_connect_by = matches!(
16578            self.config.dialect,
16579            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
16580        );
16581
16582        if !supports_connect_by && self.config.dialect.is_some() {
16583            // Add comment for unsupported dialects
16584            if self.config.pretty {
16585                self.write_newline();
16586            } else {
16587                self.write_space();
16588            }
16589            self.write_unsupported_comment(
16590                "CONNECT BY requires manual conversion to recursive CTE",
16591            )?;
16592        }
16593
16594        // Generate START WITH if present (before CONNECT BY)
16595        if let Some(start) = &connect.start {
16596            if self.config.pretty {
16597                self.write_newline();
16598            } else {
16599                self.write_space();
16600            }
16601            self.write_keyword("START WITH");
16602            self.write_space();
16603            self.generate_expression(start)?;
16604        }
16605
16606        // Generate CONNECT BY
16607        if self.config.pretty {
16608            self.write_newline();
16609        } else {
16610            self.write_space();
16611        }
16612        self.write_keyword("CONNECT BY");
16613        if connect.nocycle {
16614            self.write_space();
16615            self.write_keyword("NOCYCLE");
16616        }
16617        self.write_space();
16618        self.generate_expression(&connect.connect)?;
16619
16620        Ok(())
16621    }
16622
16623    /// Generate Connect expression (for Expression::Connect variant)
16624    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
16625        self.generate_connect(connect)
16626    }
16627
16628    /// Generate PRIOR expression
16629    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
16630        self.write_keyword("PRIOR");
16631        self.write_space();
16632        self.generate_expression(&prior.this)?;
16633        Ok(())
16634    }
16635
16636    /// Generate CONNECT_BY_ROOT function
16637    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
16638    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
16639        self.write_keyword("CONNECT_BY_ROOT");
16640        self.write_space();
16641        self.generate_expression(&cbr.this)?;
16642        Ok(())
16643    }
16644
16645    /// Generate MATCH_RECOGNIZE clause
16646    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
16647        use crate::dialects::DialectType;
16648
16649        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
16650        let supports_match_recognize = matches!(
16651            self.config.dialect,
16652            Some(DialectType::Oracle)
16653                | Some(DialectType::Snowflake)
16654                | Some(DialectType::Presto)
16655                | Some(DialectType::Trino)
16656        );
16657
16658        // Generate the source table first
16659        if let Some(source) = &mr.this {
16660            self.generate_expression(source)?;
16661        }
16662
16663        if !supports_match_recognize {
16664            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
16665            return Ok(());
16666        }
16667
16668        // In pretty mode, MATCH_RECOGNIZE should be on a new line
16669        if self.config.pretty {
16670            self.write_newline();
16671        } else {
16672            self.write_space();
16673        }
16674
16675        self.write_keyword("MATCH_RECOGNIZE");
16676        self.write(" (");
16677
16678        if self.config.pretty {
16679            self.indent_level += 1;
16680        }
16681
16682        let mut needs_separator = false;
16683
16684        // PARTITION BY
16685        if let Some(partition_by) = &mr.partition_by {
16686            if !partition_by.is_empty() {
16687                if self.config.pretty {
16688                    self.write_newline();
16689                    self.write_indent();
16690                }
16691                self.write_keyword("PARTITION BY");
16692                self.write_space();
16693                for (i, expr) in partition_by.iter().enumerate() {
16694                    if i > 0 {
16695                        self.write(", ");
16696                    }
16697                    self.generate_expression(expr)?;
16698                }
16699                needs_separator = true;
16700            }
16701        }
16702
16703        // ORDER BY
16704        if let Some(order_by) = &mr.order_by {
16705            if !order_by.is_empty() {
16706                if needs_separator {
16707                    if self.config.pretty {
16708                        self.write_newline();
16709                        self.write_indent();
16710                    } else {
16711                        self.write_space();
16712                    }
16713                } else if self.config.pretty {
16714                    self.write_newline();
16715                    self.write_indent();
16716                }
16717                self.write_keyword("ORDER BY");
16718                // In pretty mode, put each ORDER BY column on a new indented line
16719                if self.config.pretty {
16720                    self.indent_level += 1;
16721                    for (i, ordered) in order_by.iter().enumerate() {
16722                        if i > 0 {
16723                            self.write(",");
16724                        }
16725                        self.write_newline();
16726                        self.write_indent();
16727                        self.generate_ordered(ordered)?;
16728                    }
16729                    self.indent_level -= 1;
16730                } else {
16731                    self.write_space();
16732                    for (i, ordered) in order_by.iter().enumerate() {
16733                        if i > 0 {
16734                            self.write(", ");
16735                        }
16736                        self.generate_ordered(ordered)?;
16737                    }
16738                }
16739                needs_separator = true;
16740            }
16741        }
16742
16743        // MEASURES
16744        if let Some(measures) = &mr.measures {
16745            if !measures.is_empty() {
16746                if needs_separator {
16747                    if self.config.pretty {
16748                        self.write_newline();
16749                        self.write_indent();
16750                    } else {
16751                        self.write_space();
16752                    }
16753                } else if self.config.pretty {
16754                    self.write_newline();
16755                    self.write_indent();
16756                }
16757                self.write_keyword("MEASURES");
16758                // In pretty mode, put each MEASURE on a new indented line
16759                if self.config.pretty {
16760                    self.indent_level += 1;
16761                    for (i, measure) in measures.iter().enumerate() {
16762                        if i > 0 {
16763                            self.write(",");
16764                        }
16765                        self.write_newline();
16766                        self.write_indent();
16767                        // Handle RUNNING/FINAL prefix
16768                        if let Some(semantics) = &measure.window_frame {
16769                            match semantics {
16770                                MatchRecognizeSemantics::Running => {
16771                                    self.write_keyword("RUNNING");
16772                                    self.write_space();
16773                                }
16774                                MatchRecognizeSemantics::Final => {
16775                                    self.write_keyword("FINAL");
16776                                    self.write_space();
16777                                }
16778                            }
16779                        }
16780                        self.generate_expression(&measure.this)?;
16781                    }
16782                    self.indent_level -= 1;
16783                } else {
16784                    self.write_space();
16785                    for (i, measure) in measures.iter().enumerate() {
16786                        if i > 0 {
16787                            self.write(", ");
16788                        }
16789                        // Handle RUNNING/FINAL prefix
16790                        if let Some(semantics) = &measure.window_frame {
16791                            match semantics {
16792                                MatchRecognizeSemantics::Running => {
16793                                    self.write_keyword("RUNNING");
16794                                    self.write_space();
16795                                }
16796                                MatchRecognizeSemantics::Final => {
16797                                    self.write_keyword("FINAL");
16798                                    self.write_space();
16799                                }
16800                            }
16801                        }
16802                        self.generate_expression(&measure.this)?;
16803                    }
16804                }
16805                needs_separator = true;
16806            }
16807        }
16808
16809        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
16810        if let Some(rows) = &mr.rows {
16811            if needs_separator {
16812                if self.config.pretty {
16813                    self.write_newline();
16814                    self.write_indent();
16815                } else {
16816                    self.write_space();
16817                }
16818            } else if self.config.pretty {
16819                self.write_newline();
16820                self.write_indent();
16821            }
16822            match rows {
16823                MatchRecognizeRows::OneRowPerMatch => {
16824                    self.write_keyword("ONE ROW PER MATCH");
16825                }
16826                MatchRecognizeRows::AllRowsPerMatch => {
16827                    self.write_keyword("ALL ROWS PER MATCH");
16828                }
16829                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
16830                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
16831                }
16832                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
16833                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
16834                }
16835                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
16836                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
16837                }
16838            }
16839            needs_separator = true;
16840        }
16841
16842        // AFTER MATCH SKIP
16843        if let Some(after) = &mr.after {
16844            if needs_separator {
16845                if self.config.pretty {
16846                    self.write_newline();
16847                    self.write_indent();
16848                } else {
16849                    self.write_space();
16850                }
16851            } else if self.config.pretty {
16852                self.write_newline();
16853                self.write_indent();
16854            }
16855            match after {
16856                MatchRecognizeAfter::PastLastRow => {
16857                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
16858                }
16859                MatchRecognizeAfter::ToNextRow => {
16860                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
16861                }
16862                MatchRecognizeAfter::ToFirst(ident) => {
16863                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
16864                    self.write_space();
16865                    self.generate_identifier(ident)?;
16866                }
16867                MatchRecognizeAfter::ToLast(ident) => {
16868                    self.write_keyword("AFTER MATCH SKIP TO LAST");
16869                    self.write_space();
16870                    self.generate_identifier(ident)?;
16871                }
16872            }
16873            needs_separator = true;
16874        }
16875
16876        // PATTERN
16877        if let Some(pattern) = &mr.pattern {
16878            if needs_separator {
16879                if self.config.pretty {
16880                    self.write_newline();
16881                    self.write_indent();
16882                } else {
16883                    self.write_space();
16884                }
16885            } else if self.config.pretty {
16886                self.write_newline();
16887                self.write_indent();
16888            }
16889            self.write_keyword("PATTERN");
16890            self.write_space();
16891            self.write("(");
16892            self.write(pattern);
16893            self.write(")");
16894            needs_separator = true;
16895        }
16896
16897        // DEFINE
16898        if let Some(define) = &mr.define {
16899            if !define.is_empty() {
16900                if needs_separator {
16901                    if self.config.pretty {
16902                        self.write_newline();
16903                        self.write_indent();
16904                    } else {
16905                        self.write_space();
16906                    }
16907                } else if self.config.pretty {
16908                    self.write_newline();
16909                    self.write_indent();
16910                }
16911                self.write_keyword("DEFINE");
16912                // In pretty mode, put each DEFINE on a new indented line
16913                if self.config.pretty {
16914                    self.indent_level += 1;
16915                    for (i, (name, expr)) in define.iter().enumerate() {
16916                        if i > 0 {
16917                            self.write(",");
16918                        }
16919                        self.write_newline();
16920                        self.write_indent();
16921                        self.generate_identifier(name)?;
16922                        self.write(" AS ");
16923                        self.generate_expression(expr)?;
16924                    }
16925                    self.indent_level -= 1;
16926                } else {
16927                    self.write_space();
16928                    for (i, (name, expr)) in define.iter().enumerate() {
16929                        if i > 0 {
16930                            self.write(", ");
16931                        }
16932                        self.generate_identifier(name)?;
16933                        self.write(" AS ");
16934                        self.generate_expression(expr)?;
16935                    }
16936                }
16937            }
16938        }
16939
16940        if self.config.pretty {
16941            self.indent_level -= 1;
16942            self.write_newline();
16943        }
16944        self.write(")");
16945
16946        // Alias - only include AS if it was explicitly present in the input
16947        if let Some(alias) = &mr.alias {
16948            self.write(" ");
16949            if mr.alias_explicit_as {
16950                self.write_keyword("AS");
16951                self.write(" ");
16952            }
16953            self.generate_identifier(alias)?;
16954        }
16955
16956        Ok(())
16957    }
16958
16959    /// Generate a query hint /*+ ... */
16960    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
16961        use crate::dialects::DialectType;
16962
16963        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
16964        let supports_hints = matches!(
16965            self.config.dialect,
16966            None |  // No dialect = preserve everything
16967            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
16968            Some(DialectType::Spark) | Some(DialectType::Hive) |
16969            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
16970        );
16971
16972        if !supports_hints || hint.expressions.is_empty() {
16973            return Ok(());
16974        }
16975
16976        // First, expand raw hint text into individual hint strings
16977        // This handles the case where the parser stored multiple hints as a single raw string
16978        let mut hint_strings: Vec<String> = Vec::new();
16979        for expr in &hint.expressions {
16980            match expr {
16981                HintExpression::Raw(text) => {
16982                    // Parse raw hint text into individual hint function calls
16983                    let parsed = self.parse_raw_hint_text(text);
16984                    hint_strings.extend(parsed);
16985                }
16986                _ => {
16987                    hint_strings.push(self.hint_expression_to_string(expr)?);
16988                }
16989            }
16990        }
16991
16992        // In pretty mode with multiple hints, always use multiline format
16993        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
16994        // always joins with newlines in pretty mode
16995        let use_multiline = self.config.pretty && hint_strings.len() > 1;
16996
16997        if use_multiline {
16998            // Pretty print with each hint on its own line
16999            self.write(" /*+ ");
17000            for (i, hint_str) in hint_strings.iter().enumerate() {
17001                if i > 0 {
17002                    self.write_newline();
17003                    self.write("  "); // 2-space indent within hint block
17004                }
17005                self.write(hint_str);
17006            }
17007            self.write(" */");
17008        } else {
17009            // Single line format
17010            self.write(" /*+ ");
17011            let sep = match self.config.dialect {
17012                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
17013                _ => " ",
17014            };
17015            for (i, hint_str) in hint_strings.iter().enumerate() {
17016                if i > 0 {
17017                    self.write(sep);
17018                }
17019                self.write(hint_str);
17020            }
17021            self.write(" */");
17022        }
17023
17024        Ok(())
17025    }
17026
17027    /// Parse raw hint text into individual hint function calls
17028    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
17029    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
17030    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
17031        let mut results = Vec::new();
17032        let mut chars = text.chars().peekable();
17033        let mut current = String::new();
17034        let mut paren_depth = 0;
17035        let mut has_unparseable_content = false;
17036        let mut position_after_last_function = 0;
17037        let mut char_position = 0;
17038
17039        while let Some(c) = chars.next() {
17040            char_position += c.len_utf8();
17041            match c {
17042                '(' => {
17043                    paren_depth += 1;
17044                    current.push(c);
17045                }
17046                ')' => {
17047                    paren_depth -= 1;
17048                    current.push(c);
17049                    // When we close the outer parenthesis, we've completed a hint function
17050                    if paren_depth == 0 {
17051                        let trimmed = current.trim().to_string();
17052                        if !trimmed.is_empty() {
17053                            // Format this hint for pretty printing if needed
17054                            let formatted = self.format_hint_function(&trimmed);
17055                            results.push(formatted);
17056                        }
17057                        current.clear();
17058                        position_after_last_function = char_position;
17059                    }
17060                }
17061                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
17062                    // Space/comma/whitespace outside parentheses - skip
17063                }
17064                _ if paren_depth == 0 => {
17065                    // Character outside parentheses - accumulate for potential hint name
17066                    current.push(c);
17067                }
17068                _ => {
17069                    current.push(c);
17070                }
17071            }
17072        }
17073
17074        // Check if there's remaining text after the last function call
17075        let remaining_text = text[position_after_last_function..].trim();
17076        if !remaining_text.is_empty() {
17077            // Check if it looks like valid hint function names
17078            // Valid hint identifiers typically are uppercase alphanumeric with underscores
17079            // If we see multiple words without parens, it's likely unparseable
17080            let words: Vec<&str> = remaining_text.split_whitespace().collect();
17081            let looks_like_hint_functions = words.iter().all(|word| {
17082                // A valid hint name followed by opening paren, or a standalone uppercase identifier
17083                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
17084            });
17085
17086            if !looks_like_hint_functions && words.len() > 1 {
17087                has_unparseable_content = true;
17088            }
17089        }
17090
17091        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
17092        if has_unparseable_content {
17093            return vec![text.trim().to_string()];
17094        }
17095
17096        // If we couldn't parse anything, return the original text as a single hint
17097        if results.is_empty() {
17098            results.push(text.trim().to_string());
17099        }
17100
17101        results
17102    }
17103
17104    /// Format a hint function for pretty printing
17105    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
17106    fn format_hint_function(&self, hint: &str) -> String {
17107        if !self.config.pretty {
17108            return hint.to_string();
17109        }
17110
17111        // Try to parse NAME(args) pattern
17112        if let Some(paren_pos) = hint.find('(') {
17113            if hint.ends_with(')') {
17114                let name = &hint[..paren_pos];
17115                let args_str = &hint[paren_pos + 1..hint.len() - 1];
17116
17117                // Parse arguments (space-separated for Oracle hints)
17118                let args: Vec<&str> = args_str.split_whitespace().collect();
17119
17120                // Calculate total width of arguments
17121                let total_args_width: usize =
17122                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
17123
17124                // If too wide, format on multiple lines
17125                if total_args_width > self.config.max_text_width && !args.is_empty() {
17126                    let mut result = format!("{}(\n", name);
17127                    for arg in &args {
17128                        result.push_str("    "); // 4-space indent for args
17129                        result.push_str(arg);
17130                        result.push('\n');
17131                    }
17132                    result.push_str("  )"); // 2-space indent for closing paren
17133                    return result;
17134                }
17135            }
17136        }
17137
17138        hint.to_string()
17139    }
17140
17141    /// Convert a hint expression to a string, handling multiline formatting for long arguments
17142    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
17143        match expr {
17144            HintExpression::Function { name, args } => {
17145                // Generate each argument to a string
17146                let arg_strings: Vec<String> = args
17147                    .iter()
17148                    .map(|arg| {
17149                        let mut gen = Generator::with_arc_config(self.config.clone());
17150                        gen.generate_expression(arg)?;
17151                        Ok(gen.output)
17152                    })
17153                    .collect::<Result<Vec<_>>>()?;
17154
17155                // Oracle hints use space-separated arguments, not comma-separated
17156                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
17157                    + arg_strings.len().saturating_sub(1); // spaces between args
17158
17159                // Check if function args need multiline formatting
17160                // Use too_wide check for argument formatting
17161                let args_multiline =
17162                    self.config.pretty && total_args_width > self.config.max_text_width;
17163
17164                if args_multiline && !arg_strings.is_empty() {
17165                    // Multiline format for long argument lists
17166                    let mut result = format!("{}(\n", name);
17167                    for arg_str in &arg_strings {
17168                        result.push_str("    "); // 4-space indent for args
17169                        result.push_str(arg_str);
17170                        result.push('\n');
17171                    }
17172                    result.push_str("  )"); // 2-space indent for closing paren
17173                    Ok(result)
17174                } else {
17175                    // Single line format with space-separated args (Oracle style)
17176                    let args_str = arg_strings.join(" ");
17177                    Ok(format!("{}({})", name, args_str))
17178                }
17179            }
17180            HintExpression::Identifier(name) => Ok(name.clone()),
17181            HintExpression::Raw(text) => {
17182                // For pretty printing, try to format the raw text
17183                if self.config.pretty {
17184                    Ok(self.format_hint_function(text))
17185                } else {
17186                    Ok(text.clone())
17187                }
17188            }
17189        }
17190    }
17191
17192    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
17193        // PostgreSQL ONLY modifier: prevents scanning child tables
17194        if table.only {
17195            self.write_keyword("ONLY");
17196            self.write_space();
17197        }
17198
17199        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
17200        if let Some(ref identifier_func) = table.identifier_func {
17201            self.generate_expression(identifier_func)?;
17202            // If table name parts are present, emit .catalog.schema.name after the function
17203            if !table.name.name.is_empty() {
17204                if let Some(catalog) = &table.catalog {
17205                    self.write(".");
17206                    self.generate_identifier(catalog)?;
17207                }
17208                if let Some(schema) = &table.schema {
17209                    self.write(".");
17210                    self.generate_identifier(schema)?;
17211                }
17212                self.write(".");
17213                self.generate_identifier(&table.name)?;
17214            }
17215        } else {
17216            if let Some(catalog) = &table.catalog {
17217                self.generate_identifier(catalog)?;
17218                self.write(".");
17219            }
17220            if let Some(schema) = &table.schema {
17221                self.generate_identifier(schema)?;
17222                self.write(".");
17223            }
17224            self.generate_identifier(&table.name)?;
17225        }
17226
17227        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
17228        if let Some(changes) = &table.changes {
17229            self.write(" ");
17230            self.generate_changes(changes)?;
17231        }
17232
17233        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
17234        if !table.partitions.is_empty() {
17235            self.write_space();
17236            self.write_keyword("PARTITION");
17237            self.write("(");
17238            for (i, partition) in table.partitions.iter().enumerate() {
17239                if i > 0 {
17240                    self.write(", ");
17241                }
17242                self.generate_identifier(partition)?;
17243            }
17244            self.write(")");
17245        }
17246
17247        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
17248        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
17249        if table.changes.is_none() {
17250            if let Some(when) = &table.when {
17251                self.write_space();
17252                self.generate_historical_data(when)?;
17253            }
17254        }
17255
17256        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
17257        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
17258        if !system_time_post_alias {
17259            if let Some(ref system_time) = table.system_time {
17260                self.write_space();
17261                self.write(system_time);
17262            }
17263        }
17264
17265        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
17266        if let Some(ref version) = table.version {
17267            self.write_space();
17268            self.generate_version(version)?;
17269        }
17270
17271        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
17272        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
17273        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
17274        let alias_post_tablesample = self.config.alias_post_tablesample;
17275
17276        if alias_post_tablesample {
17277            // TABLESAMPLE before alias (Oracle, Hive, Spark)
17278            self.generate_table_sample_clause(table)?;
17279        }
17280
17281        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
17282        // For SQLite, INDEXED BY hints come after the alias, so skip here
17283        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
17284            && table.hints.iter().any(|h| {
17285                if let Expression::Identifier(id) = h {
17286                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
17287                } else {
17288                    false
17289                }
17290            });
17291        if !table.hints.is_empty() && !is_sqlite_hint {
17292            for hint in &table.hints {
17293                self.write_space();
17294                self.generate_expression(hint)?;
17295            }
17296        }
17297
17298        if let Some(alias) = &table.alias {
17299            self.write_space();
17300            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
17301            // Generic mode and most dialects always use AS for table aliases
17302            let always_use_as = self.config.dialect.is_none()
17303                || matches!(
17304                    self.config.dialect,
17305                    Some(DialectType::Generic)
17306                        | Some(DialectType::PostgreSQL)
17307                        | Some(DialectType::Redshift)
17308                        | Some(DialectType::Snowflake)
17309                        | Some(DialectType::BigQuery)
17310                        | Some(DialectType::DuckDB)
17311                        | Some(DialectType::Presto)
17312                        | Some(DialectType::Trino)
17313                        | Some(DialectType::TSQL)
17314                        | Some(DialectType::Fabric)
17315                        | Some(DialectType::MySQL)
17316                        | Some(DialectType::Spark)
17317                        | Some(DialectType::Hive)
17318                        | Some(DialectType::SQLite)
17319                        | Some(DialectType::Drill)
17320                );
17321            let is_stage_ref = table.name.name.starts_with('@');
17322            // Oracle never uses AS for table aliases
17323            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
17324            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
17325                self.write_keyword("AS");
17326                self.write_space();
17327            }
17328            self.generate_identifier(alias)?;
17329
17330            // Output column aliases if present: AS t(c1, c2)
17331            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
17332            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
17333                self.write("(");
17334                for (i, col_alias) in table.column_aliases.iter().enumerate() {
17335                    if i > 0 {
17336                        self.write(", ");
17337                    }
17338                    self.generate_identifier(col_alias)?;
17339                }
17340                self.write(")");
17341            }
17342        }
17343
17344        // BigQuery: FOR SYSTEM_TIME AS OF after alias
17345        if system_time_post_alias {
17346            if let Some(ref system_time) = table.system_time {
17347                self.write_space();
17348                self.write(system_time);
17349            }
17350        }
17351
17352        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
17353        if !alias_post_tablesample {
17354            self.generate_table_sample_clause(table)?;
17355        }
17356
17357        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
17358        if is_sqlite_hint {
17359            for hint in &table.hints {
17360                self.write_space();
17361                self.generate_expression(hint)?;
17362            }
17363        }
17364
17365        // ClickHouse FINAL modifier
17366        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
17367            self.write_space();
17368            self.write_keyword("FINAL");
17369        }
17370
17371        // Output trailing comments
17372        for comment in &table.trailing_comments {
17373            self.write_space();
17374            self.write_formatted_comment(comment);
17375        }
17376        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
17377        // output here - they are output by the FROM/PIVOT generator after the full expression
17378
17379        Ok(())
17380    }
17381
17382    /// Helper to output TABLESAMPLE clause for a table reference
17383    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
17384        if let Some(ref ts) = table.table_sample {
17385            self.write_space();
17386            if ts.is_using_sample {
17387                self.write_keyword("USING SAMPLE");
17388            } else {
17389                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
17390                self.write_keyword(self.config.tablesample_keywords);
17391            }
17392            self.generate_sample_body(ts)?;
17393            // Seed for table-level sample - use dialect's configured keyword
17394            if let Some(ref seed) = ts.seed {
17395                self.write_space();
17396                self.write_keyword(self.config.tablesample_seed_keyword);
17397                self.write(" (");
17398                self.generate_expression(seed)?;
17399                self.write(")");
17400            }
17401        }
17402        Ok(())
17403    }
17404
17405    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
17406        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
17407        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
17408
17409        if sr.quoted {
17410            self.write("'");
17411        }
17412
17413        self.write(&sr.name);
17414        if let Some(path) = &sr.path {
17415            self.write(path);
17416        }
17417
17418        if sr.quoted {
17419            self.write("'");
17420        }
17421
17422        // Output FILE_FORMAT and PATTERN if present
17423        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
17424        if has_options {
17425            self.write(" (");
17426            let mut first = true;
17427
17428            if let Some(file_format) = &sr.file_format {
17429                if !first {
17430                    self.write(", ");
17431                }
17432                self.write_keyword("FILE_FORMAT");
17433                self.write(" => ");
17434                self.generate_expression(file_format)?;
17435                first = false;
17436            }
17437
17438            if let Some(pattern) = &sr.pattern {
17439                if !first {
17440                    self.write(", ");
17441                }
17442                self.write_keyword("PATTERN");
17443                self.write(" => '");
17444                self.write(pattern);
17445                self.write("'");
17446            }
17447
17448            self.write(")");
17449        }
17450        Ok(())
17451    }
17452
17453    fn generate_star(&mut self, star: &Star) -> Result<()> {
17454        use crate::dialects::DialectType;
17455
17456        if let Some(table) = &star.table {
17457            self.generate_identifier(table)?;
17458            self.write(".");
17459        }
17460        self.write("*");
17461
17462        // Generate EXCLUDE/EXCEPT clause based on dialect
17463        if let Some(except) = &star.except {
17464            if !except.is_empty() {
17465                self.write_space();
17466                // Use dialect-appropriate keyword
17467                match self.config.dialect {
17468                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
17469                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
17470                        self.write_keyword("EXCLUDE")
17471                    }
17472                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
17473                }
17474                self.write(" (");
17475                for (i, col) in except.iter().enumerate() {
17476                    if i > 0 {
17477                        self.write(", ");
17478                    }
17479                    self.generate_identifier(col)?;
17480                }
17481                self.write(")");
17482            }
17483        }
17484
17485        // Generate REPLACE clause
17486        if let Some(replace) = &star.replace {
17487            if !replace.is_empty() {
17488                self.write_space();
17489                self.write_keyword("REPLACE");
17490                self.write(" (");
17491                for (i, alias) in replace.iter().enumerate() {
17492                    if i > 0 {
17493                        self.write(", ");
17494                    }
17495                    self.generate_expression(&alias.this)?;
17496                    self.write_space();
17497                    self.write_keyword("AS");
17498                    self.write_space();
17499                    self.generate_identifier(&alias.alias)?;
17500                }
17501                self.write(")");
17502            }
17503        }
17504
17505        // Generate RENAME clause (Snowflake specific)
17506        if let Some(rename) = &star.rename {
17507            if !rename.is_empty() {
17508                self.write_space();
17509                self.write_keyword("RENAME");
17510                self.write(" (");
17511                for (i, (old_name, new_name)) in rename.iter().enumerate() {
17512                    if i > 0 {
17513                        self.write(", ");
17514                    }
17515                    self.generate_identifier(old_name)?;
17516                    self.write_space();
17517                    self.write_keyword("AS");
17518                    self.write_space();
17519                    self.generate_identifier(new_name)?;
17520                }
17521                self.write(")");
17522            }
17523        }
17524
17525        // Output trailing comments
17526        for comment in &star.trailing_comments {
17527            self.write_space();
17528            self.write_formatted_comment(comment);
17529        }
17530
17531        Ok(())
17532    }
17533
17534    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
17535    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
17536        self.write("{");
17537        match expr {
17538            Expression::Star(star) => {
17539                // Generate the star (table.* or just * with optional EXCLUDE)
17540                self.generate_star(star)?;
17541            }
17542            Expression::ILike(ilike) => {
17543                // {* ILIKE 'pattern'} syntax
17544                self.generate_expression(&ilike.left)?;
17545                self.write_space();
17546                self.write_keyword("ILIKE");
17547                self.write_space();
17548                self.generate_expression(&ilike.right)?;
17549            }
17550            _ => {
17551                self.generate_expression(expr)?;
17552            }
17553        }
17554        self.write("}");
17555        Ok(())
17556    }
17557
17558    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
17559        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
17560        // to avoid duplication (comments are captured as both Column.trailing_comments
17561        // and Alias.pre_alias_comments during parsing)
17562        match &alias.this {
17563            Expression::Column(col) => {
17564                // Generate column without trailing comments - they're in pre_alias_comments
17565                if let Some(table) = &col.table {
17566                    self.generate_identifier(table)?;
17567                    self.write(".");
17568                }
17569                self.generate_identifier(&col.name)?;
17570            }
17571            _ => {
17572                self.generate_expression(&alias.this)?;
17573            }
17574        }
17575
17576        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
17577        // moves pre-alias comments to after the alias. When there are also trailing_comments,
17578        // keep pre-alias comments in their original position (between expression and AS).
17579        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
17580            for comment in &alias.pre_alias_comments {
17581                self.write_space();
17582                self.write_formatted_comment(comment);
17583            }
17584        }
17585
17586        use crate::dialects::DialectType;
17587
17588        // Determine if we should skip AS keyword for table-valued function aliases
17589        // Oracle and some other dialects don't use AS for table aliases
17590        // Note: We specifically use TableFromRows here, NOT Function, because Function
17591        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
17592        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
17593        let is_table_source = matches!(
17594            &alias.this,
17595            Expression::JSONTable(_)
17596                | Expression::XMLTable(_)
17597                | Expression::TableFromRows(_)
17598                | Expression::Unnest(_)
17599                | Expression::MatchRecognize(_)
17600                | Expression::Select(_)
17601                | Expression::Subquery(_)
17602                | Expression::Paren(_)
17603                | Expression::JoinedTable(_)
17604        );
17605        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
17606        let skip_as = is_table_source && dialect_skips_table_alias_as;
17607
17608        self.write_space();
17609        if !skip_as {
17610            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
17611                if let Some(ref alias_keyword) = alias.alias_keyword {
17612                    self.write(alias_keyword);
17613                } else {
17614                    self.write_keyword("AS");
17615                }
17616            } else {
17617                self.write_keyword("AS");
17618            }
17619            self.write_space();
17620        }
17621
17622        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
17623        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
17624
17625        // Check if we have column aliases only (no table alias name)
17626        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
17627            // Generate AS (col1, col2, ...)
17628            self.write("(");
17629            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
17630                if i > 0 {
17631                    self.write(", ");
17632                }
17633                self.generate_alias_identifier(col_alias)?;
17634            }
17635            self.write(")");
17636        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
17637            // Generate AS alias(col1, col2, ...)
17638            self.generate_alias_identifier(&alias.alias)?;
17639            self.write("(");
17640            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
17641                if i > 0 {
17642                    self.write(", ");
17643                }
17644                self.generate_alias_identifier(col_alias)?;
17645            }
17646            self.write(")");
17647        } else {
17648            // Simple alias (or BigQuery without column aliases)
17649            self.generate_alias_identifier(&alias.alias)?;
17650        }
17651
17652        // Output trailing comments (comments after the alias)
17653        for comment in &alias.trailing_comments {
17654            self.write_space();
17655            self.write_formatted_comment(comment);
17656        }
17657
17658        // Output pre-alias comments: when there are no trailing_comments, sqlglot
17659        // moves pre-alias comments to after the alias. When there are trailing_comments,
17660        // the pre-alias comments were already lost (consumed as column trailing comments
17661        // that were then used as pre_alias_comments). We always emit them after alias.
17662        if alias.trailing_comments.is_empty() {
17663            for comment in &alias.pre_alias_comments {
17664                self.write_space();
17665                self.write_formatted_comment(comment);
17666            }
17667        }
17668
17669        Ok(())
17670    }
17671
17672    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
17673        use crate::dialects::DialectType;
17674
17675        // SingleStore uses :> syntax
17676        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
17677            self.generate_expression(&cast.this)?;
17678            self.write(" :> ");
17679            self.generate_data_type(&cast.to)?;
17680            return Ok(());
17681        }
17682
17683        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
17684        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
17685            let is_unknown_type = matches!(cast.to, DataType::Unknown)
17686                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
17687            if is_unknown_type {
17688                if let Some(format) = &cast.format {
17689                    self.write_keyword("CAST");
17690                    self.write("(");
17691                    self.generate_expression(&cast.this)?;
17692                    self.write_space();
17693                    self.write_keyword("AS");
17694                    self.write_space();
17695                    self.write_keyword("FORMAT");
17696                    self.write_space();
17697                    self.generate_expression(format)?;
17698                    self.write(")");
17699                    return Ok(());
17700                }
17701            }
17702        }
17703
17704        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
17705        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
17706        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
17707            if let Some(format) = &cast.format {
17708                // Check if target type is DATE or TIMESTAMP
17709                let is_date = matches!(cast.to, DataType::Date);
17710                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
17711
17712                if is_date || is_timestamp {
17713                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
17714                    self.write_keyword(func_name);
17715                    self.write("(");
17716                    self.generate_expression(&cast.this)?;
17717                    self.write(", ");
17718
17719                    // Normalize format string for Oracle (HH -> HH12)
17720                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
17721                    if let Expression::Literal(lit) = format.as_ref() {
17722                        if let Literal::String(fmt_str) = lit.as_ref() {
17723                            let normalized = self.normalize_oracle_format(fmt_str);
17724                            self.write("'");
17725                            self.write(&normalized);
17726                            self.write("'");
17727                        }
17728                    } else {
17729                        self.generate_expression(format)?;
17730                    }
17731
17732                    self.write(")");
17733                    return Ok(());
17734                }
17735            }
17736        }
17737
17738        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
17739        // This preserves sqlglot's typed inline array literal output.
17740        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
17741            if let Expression::Array(arr) = &cast.this {
17742                self.generate_data_type(&cast.to)?;
17743                // Output just the bracket content [values] without the ARRAY prefix
17744                self.write("[");
17745                for (i, expr) in arr.expressions.iter().enumerate() {
17746                    if i > 0 {
17747                        self.write(", ");
17748                    }
17749                    self.generate_expression(expr)?;
17750                }
17751                self.write("]");
17752                return Ok(());
17753            }
17754            if matches!(&cast.this, Expression::ArrayFunc(_)) {
17755                self.generate_data_type(&cast.to)?;
17756                self.generate_expression(&cast.this)?;
17757                return Ok(());
17758            }
17759        }
17760
17761        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
17762        // convert the inner Struct to ROW(values...) format
17763        if matches!(
17764            self.config.dialect,
17765            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
17766        ) {
17767            if let Expression::Struct(ref s) = cast.this {
17768                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
17769                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
17770                    self.write_keyword("CAST");
17771                    self.write("(");
17772                    self.generate_struct_as_row(s)?;
17773                    self.write_space();
17774                    self.write_keyword("AS");
17775                    self.write_space();
17776                    self.generate_data_type(&cast.to)?;
17777                    self.write(")");
17778                    return Ok(());
17779                }
17780            }
17781        }
17782
17783        // Determine if we should use :: syntax based on dialect
17784        // PostgreSQL prefers :: for identity, most others prefer CAST()
17785        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
17786
17787        if use_double_colon {
17788            // PostgreSQL :: syntax: expr::type
17789            self.generate_expression(&cast.this)?;
17790            self.write("::");
17791            self.generate_data_type(&cast.to)?;
17792        } else {
17793            // Standard CAST() syntax
17794            self.write_keyword("CAST");
17795            self.write("(");
17796            self.generate_expression(&cast.this)?;
17797            self.write_space();
17798            self.write_keyword("AS");
17799            self.write_space();
17800            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
17801            // This matches Python sqlglot's CAST_MAPPING behavior
17802            if matches!(
17803                self.config.dialect,
17804                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
17805            ) {
17806                match &cast.to {
17807                    DataType::Custom { ref name } => {
17808                        if name.eq_ignore_ascii_case("LONGTEXT")
17809                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
17810                            || name.eq_ignore_ascii_case("TINYTEXT")
17811                            || name.eq_ignore_ascii_case("LONGBLOB")
17812                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
17813                            || name.eq_ignore_ascii_case("TINYBLOB")
17814                        {
17815                            self.write_keyword("CHAR");
17816                        } else {
17817                            self.generate_data_type(&cast.to)?;
17818                        }
17819                    }
17820                    DataType::VarChar { length, .. } => {
17821                        // MySQL CAST: VARCHAR -> CHAR
17822                        self.write_keyword("CHAR");
17823                        if let Some(n) = length {
17824                            self.write(&format!("({})", n));
17825                        }
17826                    }
17827                    DataType::Text => {
17828                        // MySQL CAST: TEXT -> CHAR
17829                        self.write_keyword("CHAR");
17830                    }
17831                    DataType::Timestamp {
17832                        precision,
17833                        timezone: false,
17834                    } => {
17835                        // MySQL CAST: TIMESTAMP -> DATETIME
17836                        self.write_keyword("DATETIME");
17837                        if let Some(p) = precision {
17838                            self.write(&format!("({})", p));
17839                        }
17840                    }
17841                    _ => {
17842                        self.generate_data_type(&cast.to)?;
17843                    }
17844                }
17845            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
17846                // Snowflake CAST: STRING -> VARCHAR
17847                match &cast.to {
17848                    DataType::String { length } => {
17849                        self.write_keyword("VARCHAR");
17850                        if let Some(n) = length {
17851                            self.write(&format!("({})", n));
17852                        }
17853                    }
17854                    _ => {
17855                        self.generate_data_type(&cast.to)?;
17856                    }
17857                }
17858            } else {
17859                self.generate_data_type(&cast.to)?;
17860            }
17861
17862            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
17863            if let Some(default) = &cast.default {
17864                self.write_space();
17865                self.write_keyword("DEFAULT");
17866                self.write_space();
17867                self.generate_expression(default)?;
17868                self.write_space();
17869                self.write_keyword("ON");
17870                self.write_space();
17871                self.write_keyword("CONVERSION");
17872                self.write_space();
17873                self.write_keyword("ERROR");
17874            }
17875
17876            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
17877            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
17878            if let Some(format) = &cast.format {
17879                // Check if Oracle dialect - use comma syntax
17880                if matches!(
17881                    self.config.dialect,
17882                    Some(crate::dialects::DialectType::Oracle)
17883                ) {
17884                    self.write(", ");
17885                } else {
17886                    self.write_space();
17887                    self.write_keyword("FORMAT");
17888                    self.write_space();
17889                }
17890                self.generate_expression(format)?;
17891            }
17892
17893            self.write(")");
17894            // Output trailing comments
17895            for comment in &cast.trailing_comments {
17896                self.write_space();
17897                self.write_formatted_comment(comment);
17898            }
17899        }
17900        Ok(())
17901    }
17902
17903    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
17904    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
17905    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
17906        self.write_keyword("ROW");
17907        self.write("(");
17908        for (i, (_, expr)) in s.fields.iter().enumerate() {
17909            if i > 0 {
17910                self.write(", ");
17911            }
17912            // Recursively convert inner Struct to ROW format
17913            if let Expression::Struct(ref inner_s) = expr {
17914                self.generate_struct_as_row(inner_s)?;
17915            } else {
17916                self.generate_expression(expr)?;
17917            }
17918        }
17919        self.write(")");
17920        Ok(())
17921    }
17922
17923    /// Normalize Oracle date/time format strings
17924    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
17925    fn normalize_oracle_format(&self, format: &str) -> String {
17926        // Replace standalone HH with HH12 (but not HH12 or HH24)
17927        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
17928        let mut result = String::new();
17929        let chars: Vec<char> = format.chars().collect();
17930        let mut i = 0;
17931
17932        while i < chars.len() {
17933            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
17934                // Check what follows HH
17935                if i + 2 < chars.len() {
17936                    let next = chars[i + 2];
17937                    if next == '1' || next == '2' {
17938                        // This is HH12 or HH24, keep as is
17939                        result.push('H');
17940                        result.push('H');
17941                        i += 2;
17942                        continue;
17943                    }
17944                }
17945                // Standalone HH -> HH12
17946                result.push_str("HH12");
17947                i += 2;
17948            } else {
17949                result.push(chars[i]);
17950                i += 1;
17951            }
17952        }
17953
17954        result
17955    }
17956
17957    /// Check if the current dialect prefers :: cast syntax
17958    /// Preserve ClickHouse's native `::` shorthand when the parser saw it.
17959    fn dialect_prefers_double_colon(&self) -> bool {
17960        matches!(self.config.dialect, Some(DialectType::ClickHouse))
17961    }
17962
17963    /// Generate MOD function - uses % operator for dialects that prefer or require it.
17964    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17965        use crate::dialects::DialectType;
17966
17967        // Several dialects prefer or require x % y instead of MOD(x, y).
17968        let use_percent_operator = matches!(
17969            self.config.dialect,
17970            Some(DialectType::Snowflake)
17971                | Some(DialectType::MySQL)
17972                | Some(DialectType::Presto)
17973                | Some(DialectType::Trino)
17974                | Some(DialectType::PostgreSQL)
17975                | Some(DialectType::DuckDB)
17976                | Some(DialectType::Hive)
17977                | Some(DialectType::Spark)
17978                | Some(DialectType::Databricks)
17979                | Some(DialectType::Athena)
17980                | Some(DialectType::TSQL)
17981                | Some(DialectType::Fabric)
17982        );
17983
17984        if use_percent_operator {
17985            // MOD(a, b) treats both arguments as grouped expressions. When
17986            // lowering to an infix operator, keep binary arguments grouped.
17987            let needs_paren = |e: &Expression| {
17988                matches!(
17989                    e,
17990                    Expression::Add(_)
17991                        | Expression::Sub(_)
17992                        | Expression::Mul(_)
17993                        | Expression::Div(_)
17994                        | Expression::Mod(_)
17995                        | Expression::ModFunc(_)
17996                )
17997            };
17998            if needs_paren(&f.this) {
17999                self.write("(");
18000                self.generate_expression(&f.this)?;
18001                self.write(")");
18002            } else {
18003                self.generate_expression(&f.this)?;
18004            }
18005            self.write(" % ");
18006            if needs_paren(&f.expression) {
18007                self.write("(");
18008                self.generate_expression(&f.expression)?;
18009                self.write(")");
18010            } else {
18011                self.generate_expression(&f.expression)?;
18012            }
18013            Ok(())
18014        } else {
18015            self.generate_binary_func("MOD", &f.this, &f.expression)
18016        }
18017    }
18018
18019    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
18020    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
18021        use crate::dialects::DialectType;
18022
18023        // Snowflake normalizes IFNULL to COALESCE
18024        let func_name = match self.config.dialect {
18025            Some(DialectType::Snowflake) => "COALESCE",
18026            _ => "IFNULL",
18027        };
18028
18029        self.generate_binary_func(func_name, &f.this, &f.expression)
18030    }
18031
18032    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
18033    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
18034        // Use original function name if preserved (for identity tests)
18035        if let Some(ref original_name) = f.original_name {
18036            return self.generate_binary_func(original_name, &f.this, &f.expression);
18037        }
18038
18039        // Otherwise, use dialect-specific function names
18040        use crate::dialects::DialectType;
18041        let func_name = match self.config.dialect {
18042            Some(DialectType::Snowflake)
18043            | Some(DialectType::ClickHouse)
18044            | Some(DialectType::PostgreSQL)
18045            | Some(DialectType::Presto)
18046            | Some(DialectType::Trino)
18047            | Some(DialectType::Athena)
18048            | Some(DialectType::DuckDB)
18049            | Some(DialectType::BigQuery)
18050            | Some(DialectType::Spark)
18051            | Some(DialectType::Databricks)
18052            | Some(DialectType::Hive) => "COALESCE",
18053            Some(DialectType::MySQL)
18054            | Some(DialectType::Doris)
18055            | Some(DialectType::StarRocks)
18056            | Some(DialectType::SingleStore)
18057            | Some(DialectType::TiDB) => "IFNULL",
18058            _ => "NVL",
18059        };
18060
18061        self.generate_binary_func(func_name, &f.this, &f.expression)
18062    }
18063
18064    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
18065    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
18066        use crate::dialects::DialectType;
18067
18068        // Snowflake normalizes STDDEV_SAMP to STDDEV
18069        let func_name = match self.config.dialect {
18070            Some(DialectType::Snowflake) => "STDDEV",
18071            _ => "STDDEV_SAMP",
18072        };
18073
18074        self.generate_agg_func(func_name, f)
18075    }
18076
18077    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
18078        self.generate_expression(&coll.this)?;
18079        self.write_space();
18080        self.write_keyword("COLLATE");
18081        self.write_space();
18082        if coll.quoted {
18083            // Single-quoted string: COLLATE 'de_DE'
18084            self.write("'");
18085            self.write(&coll.collation);
18086            self.write("'");
18087        } else if coll.double_quoted {
18088            // Double-quoted identifier: COLLATE "de_DE"
18089            self.write("\"");
18090            self.write(&coll.collation);
18091            self.write("\"");
18092        } else {
18093            // Unquoted identifier: COLLATE de_DE
18094            self.write(&coll.collation);
18095        }
18096        Ok(())
18097    }
18098
18099    fn generate_case(&mut self, case: &Case) -> Result<()> {
18100        // In pretty mode, decide whether to expand based on total text width
18101        let multiline_case = if self.config.pretty {
18102            // Build the flat representation to check width
18103            let mut statements: Vec<String> = Vec::new();
18104            let operand_str = if let Some(operand) = &case.operand {
18105                let s = self.generate_to_string(operand)?;
18106                statements.push(format!("CASE {}", s));
18107                s
18108            } else {
18109                statements.push("CASE".to_string());
18110                String::new()
18111            };
18112            let _ = operand_str;
18113            for (condition, result) in &case.whens {
18114                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
18115                statements.push(format!("THEN {}", self.generate_to_string(result)?));
18116            }
18117            if let Some(else_) = &case.else_ {
18118                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
18119            }
18120            statements.push("END".to_string());
18121            self.too_wide(&statements)
18122        } else {
18123            false
18124        };
18125
18126        self.write_keyword("CASE");
18127        if let Some(operand) = &case.operand {
18128            self.write_space();
18129            self.generate_expression(operand)?;
18130        }
18131        if multiline_case {
18132            self.indent_level += 1;
18133        }
18134        for (condition, result) in &case.whens {
18135            if multiline_case {
18136                self.write_newline();
18137                self.write_indent();
18138            } else {
18139                self.write_space();
18140            }
18141            self.write_keyword("WHEN");
18142            self.write_space();
18143            self.generate_expression(condition)?;
18144            if multiline_case {
18145                self.write_newline();
18146                self.write_indent();
18147            } else {
18148                self.write_space();
18149            }
18150            self.write_keyword("THEN");
18151            self.write_space();
18152            self.generate_expression(result)?;
18153        }
18154        if let Some(else_) = &case.else_ {
18155            if multiline_case {
18156                self.write_newline();
18157                self.write_indent();
18158            } else {
18159                self.write_space();
18160            }
18161            self.write_keyword("ELSE");
18162            self.write_space();
18163            self.generate_expression(else_)?;
18164        }
18165        if multiline_case {
18166            self.indent_level -= 1;
18167            self.write_newline();
18168            self.write_indent();
18169        } else {
18170            self.write_space();
18171        }
18172        self.write_keyword("END");
18173        // Emit any comments that were attached to the CASE keyword
18174        for comment in &case.comments {
18175            self.write(" ");
18176            self.write_formatted_comment(comment);
18177        }
18178        Ok(())
18179    }
18180
18181    fn generate_function(&mut self, func: &Function) -> Result<()> {
18182        // Normalize function name based on dialect settings
18183        let normalized_name = if func.name.eq_ignore_ascii_case("GROUPING")
18184            && func.args.len() > 1
18185            && matches!(
18186                self.config.dialect,
18187                Some(DialectType::TSQL | DialectType::Fabric)
18188            ) {
18189            Cow::Borrowed("GROUPING_ID")
18190        } else if matches!(self.config.dialect, Some(DialectType::Snowflake))
18191            && func.name.to_ascii_uppercase().starts_with("IDENTIFIER(")
18192        {
18193            Cow::Borrowed(func.name.as_str())
18194        } else {
18195            self.normalize_func_name(&func.name)
18196        };
18197
18198        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
18199        if matches!(self.config.dialect, Some(DialectType::DuckDB))
18200            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
18201        {
18202            self.write("LIST_FILTER(");
18203            self.write("[");
18204            for (i, arg) in func.args.iter().enumerate() {
18205                if i > 0 {
18206                    self.write(", ");
18207                }
18208                self.generate_expression(arg)?;
18209            }
18210            self.write("], _u -> NOT _u IS NULL)");
18211            return Ok(());
18212        }
18213
18214        // Snowflake fixtures expect TO_VARIANT applied to arrays to keep ARRAY_CONSTRUCT(...)
18215        // rather than bracket-array syntax.
18216        if matches!(self.config.dialect, Some(DialectType::Snowflake))
18217            && func.name.eq_ignore_ascii_case("TO_VARIANT")
18218            && func.args.len() == 1
18219        {
18220            let array_expressions = match &func.args[0] {
18221                Expression::ArrayFunc(arr) => Some(&arr.expressions),
18222                Expression::Array(arr) => Some(&arr.expressions),
18223                _ => None,
18224            };
18225            if let Some(expressions) = array_expressions {
18226                self.write_keyword("TO_VARIANT");
18227                self.write("(");
18228                self.write_keyword("ARRAY_CONSTRUCT");
18229                self.write("(");
18230                for (i, arg) in expressions.iter().enumerate() {
18231                    if i > 0 {
18232                        self.write(", ");
18233                    }
18234                    self.generate_expression(arg)?;
18235                }
18236                self.write(")");
18237                self.write(")");
18238                return Ok(());
18239            }
18240        }
18241
18242        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
18243        if func.name.eq_ignore_ascii_case("STRUCT")
18244            && !matches!(
18245                self.config.dialect,
18246                Some(DialectType::BigQuery)
18247                    | Some(DialectType::Spark)
18248                    | Some(DialectType::Databricks)
18249                    | Some(DialectType::Hive)
18250                    | None
18251            )
18252        {
18253            return self.generate_struct_function_cross_dialect(func);
18254        }
18255
18256        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
18257        // This is an internal marker function for ::? JSON path syntax
18258        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
18259            self.generate_expression(&func.args[0])?;
18260            self.write("::?");
18261            // Extract the key from the string literal
18262            if let Expression::Literal(lit) = &func.args[1] {
18263                if let crate::expressions::Literal::String(key) = lit.as_ref() {
18264                    self.write(key);
18265                }
18266            } else {
18267                self.generate_expression(&func.args[1])?;
18268            }
18269            return Ok(());
18270        }
18271
18272        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
18273        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
18274            self.generate_expression(&func.args[0])?;
18275            self.write(" # ");
18276            self.generate_expression(&func.args[1])?;
18277            return Ok(());
18278        }
18279
18280        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
18281        if matches!(
18282            self.config.dialect,
18283            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
18284        ) && func.name.eq_ignore_ascii_case("TRY")
18285            && func.args.len() == 1
18286        {
18287            self.generate_expression(&func.args[0])?;
18288            return Ok(());
18289        }
18290
18291        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
18292        if self.config.dialect == Some(DialectType::ClickHouse)
18293            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
18294            && func.args.len() == 1
18295        {
18296            self.write("dateTrunc('DAY', ");
18297            self.generate_expression(&func.args[0])?;
18298            self.write(")");
18299            return Ok(());
18300        }
18301
18302        // ClickHouse uses dateTrunc casing.
18303        if self.config.dialect == Some(DialectType::ClickHouse)
18304            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
18305            && func.args.len() == 2
18306        {
18307            self.write("dateTrunc(");
18308            self.generate_expression(&func.args[0])?;
18309            self.write(", ");
18310            self.generate_expression(&func.args[1])?;
18311            self.write(")");
18312            return Ok(());
18313        }
18314
18315        // Presto-family dialects spell SUBSTRING as SUBSTR in SQLGlot outputs.
18316        if matches!(
18317            self.config.dialect,
18318            Some(DialectType::Presto | DialectType::Trino | DialectType::Athena)
18319        ) && func.name.eq_ignore_ascii_case("SUBSTRING")
18320        {
18321            self.write_keyword("SUBSTR");
18322            self.write("(");
18323            for (i, arg) in func.args.iter().enumerate() {
18324                if i > 0 {
18325                    self.write(", ");
18326                }
18327                self.generate_expression(arg)?;
18328            }
18329            self.write(")");
18330            return Ok(());
18331        }
18332
18333        if self.config.dialect == Some(DialectType::Snowflake)
18334            && func.name.eq_ignore_ascii_case("LIST_DISTINCT")
18335            && func.args.len() == 1
18336        {
18337            self.write_keyword("ARRAY_DISTINCT");
18338            self.write("(");
18339            self.write_keyword("ARRAY_COMPACT");
18340            self.write("(");
18341            self.generate_expression(&func.args[0])?;
18342            self.write("))");
18343            return Ok(());
18344        }
18345
18346        if self.config.dialect == Some(DialectType::Snowflake)
18347            && func.name.eq_ignore_ascii_case("LIST")
18348            && func.args.len() == 1
18349            && !matches!(func.args.first(), Some(Expression::Select(_)))
18350        {
18351            self.write_keyword("ARRAY_AGG");
18352            self.write("(");
18353            self.generate_expression(&func.args[0])?;
18354            self.write(")");
18355            return Ok(());
18356        }
18357
18358        // Redshift: CONCAT(a, b, ...) -> a || b || ...
18359        if self.config.dialect == Some(DialectType::Redshift)
18360            && func.name.eq_ignore_ascii_case("CONCAT")
18361            && func.args.len() >= 2
18362        {
18363            for (i, arg) in func.args.iter().enumerate() {
18364                if i > 0 {
18365                    self.write(" || ");
18366                }
18367                self.generate_expression(arg)?;
18368            }
18369            return Ok(());
18370        }
18371
18372        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
18373        if self.config.dialect == Some(DialectType::Redshift)
18374            && func.name.eq_ignore_ascii_case("CONCAT_WS")
18375            && func.args.len() >= 2
18376        {
18377            let sep = &func.args[0];
18378            for (i, arg) in func.args.iter().skip(1).enumerate() {
18379                if i > 0 {
18380                    self.write(" || ");
18381                    self.generate_expression(sep)?;
18382                    self.write(" || ");
18383                }
18384                self.generate_expression(arg)?;
18385            }
18386            return Ok(());
18387        }
18388
18389        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
18390        // Unit should be unquoted uppercase identifier
18391        if self.config.dialect == Some(DialectType::Redshift)
18392            && (func.name.eq_ignore_ascii_case("DATEDIFF")
18393                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
18394            && func.args.len() == 3
18395        {
18396            self.write_keyword("DATEDIFF");
18397            self.write("(");
18398            // First arg is unit - normalize to unquoted uppercase
18399            self.write_redshift_date_part(&func.args[0]);
18400            self.write(", ");
18401            self.generate_expression(&func.args[1])?;
18402            self.write(", ");
18403            self.generate_expression(&func.args[2])?;
18404            self.write(")");
18405            return Ok(());
18406        }
18407
18408        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
18409        // Unit should be unquoted uppercase identifier
18410        if self.config.dialect == Some(DialectType::Redshift)
18411            && (func.name.eq_ignore_ascii_case("DATEADD")
18412                || func.name.eq_ignore_ascii_case("DATE_ADD"))
18413            && func.args.len() == 3
18414        {
18415            self.write_keyword("DATEADD");
18416            self.write("(");
18417            // First arg is unit - normalize to unquoted uppercase
18418            self.write_redshift_date_part(&func.args[0]);
18419            self.write(", ");
18420            self.generate_expression(&func.args[1])?;
18421            self.write(", ");
18422            self.generate_expression(&func.args[2])?;
18423            self.write(")");
18424            return Ok(());
18425        }
18426
18427        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function.
18428        if func.name.eq_ignore_ascii_case("UUID_STRING")
18429            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
18430        {
18431            if matches!(
18432                self.config.dialect,
18433                Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
18434            ) {
18435                self.write_keyword("CAST");
18436                self.write("(");
18437                self.write_keyword("UUID");
18438                self.write("() ");
18439                self.write_keyword("AS");
18440                self.write(" ");
18441                self.write_keyword("STRING");
18442                self.write(")");
18443                return Ok(());
18444            }
18445
18446            if matches!(
18447                self.config.dialect,
18448                Some(DialectType::Presto | DialectType::Trino)
18449            ) {
18450                self.write_keyword("CAST");
18451                self.write("(");
18452                self.write_keyword("UUID");
18453                self.write("() ");
18454                self.write_keyword("AS");
18455                self.write(" ");
18456                self.write_keyword("VARCHAR");
18457                self.write(")");
18458                return Ok(());
18459            }
18460
18461            if self.config.dialect == Some(DialectType::DuckDB) && func.args.len() == 2 {
18462                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(");
18463                self.generate_expression(&func.args[0])?;
18464                self.write(", '-', '')) || ENCODE(");
18465                self.generate_expression(&func.args[1])?;
18466                self.write(")), 1, 32) AS h))");
18467                return Ok(());
18468            }
18469
18470            let func_name = match self.config.dialect {
18471                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
18472                Some(DialectType::BigQuery) => "GENERATE_UUID",
18473                _ => "UUID",
18474            };
18475            self.write_keyword(func_name);
18476            self.write("()");
18477            return Ok(());
18478        }
18479
18480        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
18481        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
18482        // Positional args are mapped to named parameters.
18483        if matches!(self.config.dialect, Some(DialectType::Snowflake))
18484            && func.name.eq_ignore_ascii_case("GENERATOR")
18485        {
18486            let has_positional_args =
18487                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
18488            if has_positional_args {
18489                let param_names = ["ROWCOUNT", "TIMELIMIT"];
18490                self.write_keyword("GENERATOR");
18491                self.write("(");
18492                for (i, arg) in func.args.iter().enumerate() {
18493                    if i > 0 {
18494                        self.write(", ");
18495                    }
18496                    if i < param_names.len() {
18497                        self.write_keyword(param_names[i]);
18498                        self.write(" => ");
18499                        self.generate_expression(arg)?;
18500                    } else {
18501                        self.generate_expression(arg)?;
18502                    }
18503                }
18504                self.write(")");
18505                return Ok(());
18506            }
18507        }
18508
18509        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
18510        // Unit should be quoted uppercase string
18511        if self.config.dialect == Some(DialectType::Redshift)
18512            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
18513            && func.args.len() == 2
18514        {
18515            self.write_keyword("DATE_TRUNC");
18516            self.write("(");
18517            // First arg is unit - normalize to quoted uppercase
18518            self.write_redshift_date_part_quoted(&func.args[0]);
18519            self.write(", ");
18520            self.generate_expression(&func.args[1])?;
18521            self.write(")");
18522            return Ok(());
18523        }
18524
18525        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
18526        if matches!(
18527            self.config.dialect,
18528            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18529        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
18530            || func.name.eq_ignore_ascii_case("DATEPART"))
18531            && func.args.len() == 2
18532        {
18533            if let Some(part) = self.extract_date_part_string(&func.args[0]) {
18534                let date_part = self.classify_tsql_date_part_name(&part);
18535                self.generate_tsql_date_part(date_part, &func.args[1])?;
18536            } else {
18537                self.write_keyword("DATEPART");
18538                self.write("(");
18539                self.generate_expression(&func.args[0])?;
18540                self.write(", ");
18541                self.generate_expression(&func.args[1])?;
18542                self.write(")");
18543            }
18544            return Ok(());
18545        }
18546
18547        // TSQL/Fabric: DATETRUNC(datepart, value) requires an unquoted datepart keyword.
18548        if matches!(
18549            self.config.dialect,
18550            Some(DialectType::TSQL) | Some(DialectType::Fabric)
18551        ) && (func.name.eq_ignore_ascii_case("DATETRUNC")
18552            || func.name.eq_ignore_ascii_case("DATE_TRUNC"))
18553            && func.args.len() == 2
18554        {
18555            if let Some(part) = self.extract_date_part_string(&func.args[0]) {
18556                let date_part = self.classify_tsql_date_trunc_name(&part);
18557                self.generate_tsql_date_trunc(date_part, &func.args[1])?;
18558            } else {
18559                self.write_keyword("DATETRUNC");
18560                self.write("(");
18561                self.generate_expression(&func.args[0])?;
18562                self.write(", ");
18563                self.generate_expression(&func.args[1])?;
18564                self.write(")");
18565            }
18566            return Ok(());
18567        }
18568
18569        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
18570        if matches!(
18571            self.config.dialect,
18572            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
18573        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
18574            || func.name.eq_ignore_ascii_case("DATEPART"))
18575            && func.args.len() == 2
18576        {
18577            self.write_keyword("EXTRACT");
18578            self.write("(");
18579            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
18580            match &func.args[0] {
18581                Expression::Literal(lit)
18582                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18583                {
18584                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
18585                        unreachable!()
18586                    };
18587                    self.write(&s.to_ascii_lowercase());
18588                }
18589                _ => self.generate_expression(&func.args[0])?,
18590            }
18591            self.write_space();
18592            self.write_keyword("FROM");
18593            self.write_space();
18594            self.generate_expression(&func.args[1])?;
18595            self.write(")");
18596            return Ok(());
18597        }
18598
18599        // PostgreSQL: DATE_ADD(date, INTERVAL '...') / DATE_SUB(...) -> infix interval arithmetic.
18600        if self.config.dialect == Some(DialectType::PostgreSQL)
18601            && matches!(
18602                func.name.to_ascii_uppercase().as_str(),
18603                "DATE_ADD" | "DATE_SUB"
18604            )
18605            && func.args.len() == 2
18606            && matches!(func.args[1], Expression::Interval(_))
18607        {
18608            self.generate_expression(&func.args[0])?;
18609            self.write_space();
18610            if func.name.eq_ignore_ascii_case("DATE_SUB") {
18611                self.write("-");
18612            } else {
18613                self.write("+");
18614            }
18615            self.write_space();
18616            self.generate_expression(&func.args[1])?;
18617            return Ok(());
18618        }
18619
18620        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
18621        // Also DATE literals in Dremio should be CAST(...AS DATE)
18622        if self.config.dialect == Some(DialectType::Dremio)
18623            && (func.name.eq_ignore_ascii_case("DATE_PART")
18624                || func.name.eq_ignore_ascii_case("DATEPART"))
18625            && func.args.len() == 2
18626        {
18627            self.write_keyword("EXTRACT");
18628            self.write("(");
18629            self.generate_expression(&func.args[0])?;
18630            self.write_space();
18631            self.write_keyword("FROM");
18632            self.write_space();
18633            // For Dremio, DATE literals should become CAST('value' AS DATE)
18634            self.generate_dremio_date_expression(&func.args[1])?;
18635            self.write(")");
18636            return Ok(());
18637        }
18638
18639        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
18640        if self.config.dialect == Some(DialectType::Dremio)
18641            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
18642            && func.args.is_empty()
18643        {
18644            self.write_keyword("CURRENT_DATE_UTC");
18645            return Ok(());
18646        }
18647
18648        // Dremio: DATETYPE(year, month, day) transformation
18649        // - If all args are integer literals: DATE('YYYY-MM-DD')
18650        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
18651        if self.config.dialect == Some(DialectType::Dremio)
18652            && func.name.eq_ignore_ascii_case("DATETYPE")
18653            && func.args.len() == 3
18654        {
18655            // Helper function to extract integer from number literal
18656            fn get_int_literal(expr: &Expression) -> Option<i64> {
18657                if let Expression::Literal(lit) = expr {
18658                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
18659                        s.parse::<i64>().ok()
18660                    } else {
18661                        None
18662                    }
18663                } else {
18664                    None
18665                }
18666            }
18667
18668            // Check if all arguments are integer literals
18669            if let (Some(year), Some(month), Some(day)) = (
18670                get_int_literal(&func.args[0]),
18671                get_int_literal(&func.args[1]),
18672                get_int_literal(&func.args[2]),
18673            ) {
18674                // All are integer literals: DATE('YYYY-MM-DD')
18675                self.write_keyword("DATE");
18676                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
18677                return Ok(());
18678            }
18679
18680            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
18681            self.write_keyword("CAST");
18682            self.write("(");
18683            self.write_keyword("CONCAT");
18684            self.write("(");
18685            self.generate_expression(&func.args[0])?;
18686            self.write(", '-', ");
18687            self.generate_expression(&func.args[1])?;
18688            self.write(", '-', ");
18689            self.generate_expression(&func.args[2])?;
18690            self.write(")");
18691            self.write_space();
18692            self.write_keyword("AS");
18693            self.write_space();
18694            self.write_keyword("DATE");
18695            self.write(")");
18696            return Ok(());
18697        }
18698
18699        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
18700        // when it's not an integer literal
18701        let is_presto_like = matches!(
18702            self.config.dialect,
18703            Some(DialectType::Presto) | Some(DialectType::Trino)
18704        );
18705        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
18706            self.write_keyword("DATE_ADD");
18707            self.write("(");
18708            // First arg: unit (pass through as-is, e.g., 'DAY')
18709            self.generate_expression(&func.args[0])?;
18710            self.write(", ");
18711            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
18712            let interval = &func.args[1];
18713            let needs_cast = !self.returns_integer_type(interval);
18714            if needs_cast {
18715                self.write_keyword("CAST");
18716                self.write("(");
18717            }
18718            self.generate_expression(interval)?;
18719            if needs_cast {
18720                self.write_space();
18721                self.write_keyword("AS");
18722                self.write_space();
18723                self.write_keyword("BIGINT");
18724                self.write(")");
18725            }
18726            self.write(", ");
18727            // Third arg: date
18728            self.generate_expression(&func.args[2])?;
18729            self.write(")");
18730            return Ok(());
18731        }
18732
18733        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
18734        let use_brackets = func.use_bracket_syntax;
18735
18736        // Special case: functions WITH ORDINALITY need special output order
18737        // Input: FUNC(args) WITH ORDINALITY
18738        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
18739        // Output must be: FUNC(args) WITH ORDINALITY
18740        let has_ordinality = func.name.len() >= 16
18741            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
18742        let output_name = if has_ordinality {
18743            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
18744            self.normalize_func_name(base_name)
18745        } else {
18746            normalized_name.clone()
18747        };
18748
18749        // For qualified names (schema.function or object.method), preserve original case
18750        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
18751        let quote_source_clickhouse_function =
18752            matches!(self.config.dialect, Some(DialectType::ClickHouse))
18753                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
18754                && func.quoted;
18755
18756        if quote_source_clickhouse_function {
18757            self.generate_identifier(&Identifier {
18758                name: func.name.clone(),
18759                quoted: true,
18760                trailing_comments: Vec::new(),
18761                span: None,
18762            })?;
18763        } else if func.name.contains('.') && !has_ordinality {
18764            // Don't normalize qualified functions - preserve original case
18765            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
18766            if func.quoted {
18767                self.write("`");
18768                self.write(&func.name);
18769                self.write("`");
18770            } else {
18771                self.write(&func.name);
18772            }
18773        } else {
18774            self.write(&output_name);
18775        }
18776
18777        // If no_parens is true and there are no args, output just the function name
18778        // Unless the target dialect requires parens for this function
18779        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
18780            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
18781                || func.name.eq_ignore_ascii_case("SESSION_USER")
18782                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
18783            {
18784                matches!(
18785                    self.config.dialect,
18786                    Some(DialectType::Snowflake)
18787                        | Some(DialectType::Spark)
18788                        | Some(DialectType::Databricks)
18789                        | Some(DialectType::Hive)
18790                )
18791            } else {
18792                false
18793            };
18794            !needs_parens
18795        };
18796        if force_parens {
18797            // Output trailing comments
18798            for comment in &func.trailing_comments {
18799                self.write_space();
18800                self.write_formatted_comment(comment);
18801            }
18802            return Ok(());
18803        }
18804
18805        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
18806        if func.name.eq_ignore_ascii_case("CUBE")
18807            || func.name.eq_ignore_ascii_case("ROLLUP")
18808            || func.name.eq_ignore_ascii_case("GROUPING SETS")
18809        {
18810            self.write(" (");
18811        } else if use_brackets {
18812            self.write("[");
18813        } else {
18814            self.write("(");
18815        }
18816        if func.distinct {
18817            self.write_keyword("DISTINCT");
18818            self.write_space();
18819        }
18820
18821        // Check if arguments should be split onto multiple lines (pretty + too wide)
18822        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
18823            && (func.name.eq_ignore_ascii_case("TABLE")
18824                || func.name.eq_ignore_ascii_case("FLATTEN"));
18825        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
18826        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
18827            || func.name.eq_ignore_ascii_case("CUBE")
18828            || func.name.eq_ignore_ascii_case("ROLLUP");
18829        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
18830            if is_grouping_func {
18831                true
18832            } else {
18833                // Pre-render arguments to check total width
18834                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
18835                for arg in &func.args {
18836                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
18837                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
18838                    temp_gen.generate_expression(arg)?;
18839                    expr_strings.push(temp_gen.output);
18840                }
18841                self.too_wide(&expr_strings)
18842            }
18843        } else {
18844            false
18845        };
18846
18847        if should_split {
18848            // Split onto multiple lines
18849            self.write_newline();
18850            self.indent_level += 1;
18851            for (i, arg) in func.args.iter().enumerate() {
18852                self.write_indent();
18853                self.generate_expression(arg)?;
18854                if i + 1 < func.args.len() {
18855                    self.write(",");
18856                }
18857                self.write_newline();
18858            }
18859            self.indent_level -= 1;
18860            self.write_indent();
18861        } else {
18862            // All on one line
18863            for (i, arg) in func.args.iter().enumerate() {
18864                if i > 0 {
18865                    self.write(", ");
18866                }
18867                self.generate_expression(arg)?;
18868            }
18869        }
18870
18871        if use_brackets {
18872            self.write("]");
18873        } else {
18874            self.write(")");
18875        }
18876        // Append WITH ORDINALITY after closing paren for table-valued functions
18877        if has_ordinality {
18878            self.write_space();
18879            self.write_keyword("WITH ORDINALITY");
18880        }
18881        // Output trailing comments
18882        for comment in &func.trailing_comments {
18883            self.write_space();
18884            self.write_formatted_comment(comment);
18885        }
18886        Ok(())
18887    }
18888
18889    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
18890        self.generate_expression(&fe.this)?;
18891        self.write_keyword(" EMITS ");
18892        self.generate_expression(&fe.emits)?;
18893        Ok(())
18894    }
18895
18896    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
18897        // Normalize function name based on dialect settings
18898        let mut normalized_name = self.normalize_func_name(&func.name);
18899
18900        // Dialect-specific name mappings for aggregate functions
18901        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
18902            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
18903            match self.config.dialect {
18904                Some(DialectType::ClickHouse) => {
18905                    normalized_name = if is_max {
18906                        Cow::Borrowed("argMax")
18907                    } else {
18908                        Cow::Borrowed("argMin")
18909                    };
18910                }
18911                Some(DialectType::DuckDB) => {
18912                    normalized_name = if is_max {
18913                        Cow::Borrowed("ARG_MAX")
18914                    } else {
18915                        Cow::Borrowed("ARG_MIN")
18916                    };
18917                }
18918                _ => {}
18919            }
18920        }
18921        self.write(normalized_name.as_ref());
18922        self.write("(");
18923        if func.distinct {
18924            self.write_keyword("DISTINCT");
18925            self.write_space();
18926        }
18927
18928        // Check if we need to transform multi-arg COUNT DISTINCT
18929        // When dialect doesn't support multi_arg_distinct, transform:
18930        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
18931        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
18932        let needs_multi_arg_transform =
18933            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
18934
18935        if needs_multi_arg_transform {
18936            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
18937            self.write_keyword("CASE");
18938            for arg in &func.args {
18939                self.write_space();
18940                self.write_keyword("WHEN");
18941                self.write_space();
18942                self.generate_expression(arg)?;
18943                self.write_space();
18944                self.write_keyword("IS NULL THEN NULL");
18945            }
18946            self.write_space();
18947            self.write_keyword("ELSE");
18948            self.write(" (");
18949            for (i, arg) in func.args.iter().enumerate() {
18950                if i > 0 {
18951                    self.write(", ");
18952                }
18953                self.generate_expression(arg)?;
18954            }
18955            self.write(")");
18956            self.write_space();
18957            self.write_keyword("END");
18958        } else {
18959            for (i, arg) in func.args.iter().enumerate() {
18960                if i > 0 {
18961                    self.write(", ");
18962                }
18963                self.generate_expression(arg)?;
18964            }
18965        }
18966
18967        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
18968        let clickhouse_ignore_nulls_outside =
18969            matches!(self.config.dialect, Some(DialectType::ClickHouse));
18970        if self.config.ignore_nulls_in_func
18971            && !matches!(
18972                self.config.dialect,
18973                Some(DialectType::DuckDB) | Some(DialectType::ClickHouse)
18974            )
18975        {
18976            if let Some(ignore) = func.ignore_nulls {
18977                self.write_space();
18978                if ignore {
18979                    self.write_keyword("IGNORE NULLS");
18980                } else {
18981                    self.write_keyword("RESPECT NULLS");
18982                }
18983            }
18984        }
18985
18986        // ORDER BY inside aggregate
18987        if !func.order_by.is_empty() {
18988            self.write_space();
18989            self.write_keyword("ORDER BY");
18990            self.write_space();
18991            for (i, ord) in func.order_by.iter().enumerate() {
18992                if i > 0 {
18993                    self.write(", ");
18994                }
18995                self.generate_ordered(ord)?;
18996            }
18997        }
18998
18999        // LIMIT inside aggregate
19000        if let Some(limit) = &func.limit {
19001            self.write_space();
19002            self.write_keyword("LIMIT");
19003            self.write_space();
19004            // Check if this is a Tuple representing LIMIT offset, count
19005            if let Expression::Tuple(t) = limit.as_ref() {
19006                if t.expressions.len() == 2 {
19007                    self.generate_expression(&t.expressions[0])?;
19008                    self.write(", ");
19009                    self.generate_expression(&t.expressions[1])?;
19010                } else {
19011                    self.generate_expression(limit)?;
19012                }
19013            } else {
19014                self.generate_expression(limit)?;
19015            }
19016        }
19017
19018        self.write(")");
19019
19020        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
19021        if (!self.config.ignore_nulls_in_func || clickhouse_ignore_nulls_outside)
19022            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
19023        {
19024            if let Some(ignore) = func.ignore_nulls {
19025                self.write_space();
19026                if ignore {
19027                    self.write_keyword("IGNORE NULLS");
19028                } else {
19029                    self.write_keyword("RESPECT NULLS");
19030                }
19031            }
19032        }
19033
19034        if let Some(filter) = &func.filter {
19035            self.write_space();
19036            self.write_keyword("FILTER");
19037            self.write("(");
19038            self.write_keyword("WHERE");
19039            self.write_space();
19040            self.generate_expression(filter)?;
19041            self.write(")");
19042        }
19043
19044        Ok(())
19045    }
19046
19047    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
19048        self.generate_expression(&wf.this)?;
19049
19050        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
19051        if let Some(keep) = &wf.keep {
19052            self.write_space();
19053            self.write_keyword("KEEP");
19054            self.write(" (");
19055            self.write_keyword("DENSE_RANK");
19056            self.write_space();
19057            if keep.first {
19058                self.write_keyword("FIRST");
19059            } else {
19060                self.write_keyword("LAST");
19061            }
19062            self.write_space();
19063            self.write_keyword("ORDER BY");
19064            self.write_space();
19065            for (i, ord) in keep.order_by.iter().enumerate() {
19066                if i > 0 {
19067                    self.write(", ");
19068                }
19069                self.generate_ordered(ord)?;
19070            }
19071            self.write(")");
19072        }
19073
19074        // Check if there's any OVER clause content
19075        let has_over = !wf.over.partition_by.is_empty()
19076            || !wf.over.order_by.is_empty()
19077            || wf.over.frame.is_some()
19078            || wf.over.window_name.is_some();
19079
19080        // Only output OVER if there's actual window specification (not just KEEP alone)
19081        if has_over {
19082            self.write_space();
19083            self.write_keyword("OVER");
19084
19085            // Check if this is just a bare named window reference (no parens needed)
19086            let has_specs = !wf.over.partition_by.is_empty()
19087                || !wf.over.order_by.is_empty()
19088                || wf.over.frame.is_some();
19089
19090            if wf.over.window_name.is_some() && !has_specs {
19091                // OVER window_name (without parentheses)
19092                self.write_space();
19093                self.write(&wf.over.window_name.as_ref().unwrap().name);
19094            } else {
19095                // OVER (...) or OVER (window_name ...)
19096                self.write(" (");
19097                self.generate_over(&wf.over)?;
19098                self.write(")");
19099            }
19100        } else if wf.keep.is_none() {
19101            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
19102            self.write_space();
19103            self.write_keyword("OVER");
19104            self.write(" ()");
19105        }
19106
19107        Ok(())
19108    }
19109
19110    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
19111    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
19112        self.generate_expression(&wg.this)?;
19113        self.write_space();
19114        self.write_keyword("WITHIN GROUP");
19115        self.write(" (");
19116        self.write_keyword("ORDER BY");
19117        self.write_space();
19118        for (i, ord) in wg.order_by.iter().enumerate() {
19119            if i > 0 {
19120                self.write(", ");
19121            }
19122            self.generate_ordered(ord)?;
19123        }
19124        self.write(")");
19125        Ok(())
19126    }
19127
19128    /// Generate the contents of an OVER clause (without parentheses)
19129    fn generate_over(&mut self, over: &Over) -> Result<()> {
19130        let mut has_content = false;
19131
19132        // Named window reference
19133        if let Some(name) = &over.window_name {
19134            self.write(&name.name);
19135            has_content = true;
19136        }
19137
19138        // PARTITION BY
19139        if !over.partition_by.is_empty() {
19140            if has_content {
19141                self.write_space();
19142            }
19143            self.write_keyword("PARTITION BY");
19144            self.write_space();
19145            for (i, expr) in over.partition_by.iter().enumerate() {
19146                if i > 0 {
19147                    self.write(", ");
19148                }
19149                self.generate_expression(expr)?;
19150            }
19151            has_content = true;
19152        }
19153
19154        // ORDER BY
19155        if !over.order_by.is_empty() {
19156            if has_content {
19157                self.write_space();
19158            }
19159            self.write_keyword("ORDER BY");
19160            self.write_space();
19161            for (i, ordered) in over.order_by.iter().enumerate() {
19162                if i > 0 {
19163                    self.write(", ");
19164                }
19165                self.generate_ordered(ordered)?;
19166            }
19167            has_content = true;
19168        }
19169
19170        // Window frame
19171        if let Some(frame) = &over.frame {
19172            if has_content {
19173                self.write_space();
19174            }
19175            self.generate_window_frame(frame)?;
19176        }
19177
19178        Ok(())
19179    }
19180
19181    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
19182        // Exasol uses lowercase for frame kind (rows/range/groups)
19183        let lowercase_frame = self.config.lowercase_window_frame_keywords;
19184
19185        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
19186        if !lowercase_frame {
19187            if let Some(kind_text) = &frame.kind_text {
19188                self.write(kind_text);
19189            } else {
19190                match frame.kind {
19191                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
19192                    WindowFrameKind::Range => self.write_keyword("RANGE"),
19193                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
19194                }
19195            }
19196        } else {
19197            match frame.kind {
19198                WindowFrameKind::Rows => self.write("rows"),
19199                WindowFrameKind::Range => self.write("range"),
19200                WindowFrameKind::Groups => self.write("groups"),
19201            }
19202        }
19203
19204        // Use BETWEEN format only when there's an explicit end bound,
19205        // or when normalize_window_frame_between is enabled and the start is a directional bound
19206        self.write_space();
19207        let should_normalize = self.config.normalize_window_frame_between
19208            && frame.end.is_none()
19209            && matches!(
19210                frame.start,
19211                WindowFrameBound::Preceding(_)
19212                    | WindowFrameBound::Following(_)
19213                    | WindowFrameBound::UnboundedPreceding
19214                    | WindowFrameBound::UnboundedFollowing
19215            );
19216
19217        if let Some(end) = &frame.end {
19218            // BETWEEN format: RANGE BETWEEN start AND end
19219            self.write_keyword("BETWEEN");
19220            self.write_space();
19221            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
19222            self.write_space();
19223            self.write_keyword("AND");
19224            self.write_space();
19225            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
19226        } else if should_normalize {
19227            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
19228            self.write_keyword("BETWEEN");
19229            self.write_space();
19230            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
19231            self.write_space();
19232            self.write_keyword("AND");
19233            self.write_space();
19234            self.write_keyword("CURRENT ROW");
19235        } else {
19236            // Single bound format: RANGE CURRENT ROW
19237            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
19238        }
19239
19240        // EXCLUDE clause
19241        if let Some(exclude) = &frame.exclude {
19242            self.write_space();
19243            self.write_keyword("EXCLUDE");
19244            self.write_space();
19245            match exclude {
19246                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
19247                WindowFrameExclude::Group => self.write_keyword("GROUP"),
19248                WindowFrameExclude::Ties => self.write_keyword("TIES"),
19249                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
19250            }
19251        }
19252
19253        Ok(())
19254    }
19255
19256    fn generate_window_frame_bound(
19257        &mut self,
19258        bound: &WindowFrameBound,
19259        side_text: Option<&str>,
19260    ) -> Result<()> {
19261        // Exasol uses lowercase for preceding/following
19262        let lowercase_frame = self.config.lowercase_window_frame_keywords;
19263
19264        match bound {
19265            WindowFrameBound::CurrentRow => {
19266                self.write_keyword("CURRENT ROW");
19267            }
19268            WindowFrameBound::UnboundedPreceding => {
19269                self.write_keyword("UNBOUNDED");
19270                self.write_space();
19271                if lowercase_frame {
19272                    self.write("preceding");
19273                } else if let Some(text) = side_text {
19274                    self.write(text);
19275                } else {
19276                    self.write_keyword("PRECEDING");
19277                }
19278            }
19279            WindowFrameBound::UnboundedFollowing => {
19280                self.write_keyword("UNBOUNDED");
19281                self.write_space();
19282                if lowercase_frame {
19283                    self.write("following");
19284                } else if let Some(text) = side_text {
19285                    self.write(text);
19286                } else {
19287                    self.write_keyword("FOLLOWING");
19288                }
19289            }
19290            WindowFrameBound::Preceding(expr) => {
19291                self.generate_expression(expr)?;
19292                self.write_space();
19293                if lowercase_frame {
19294                    self.write("preceding");
19295                } else if let Some(text) = side_text {
19296                    self.write(text);
19297                } else {
19298                    self.write_keyword("PRECEDING");
19299                }
19300            }
19301            WindowFrameBound::Following(expr) => {
19302                self.generate_expression(expr)?;
19303                self.write_space();
19304                if lowercase_frame {
19305                    self.write("following");
19306                } else if let Some(text) = side_text {
19307                    self.write(text);
19308                } else {
19309                    self.write_keyword("FOLLOWING");
19310                }
19311            }
19312            WindowFrameBound::BarePreceding => {
19313                if lowercase_frame {
19314                    self.write("preceding");
19315                } else if let Some(text) = side_text {
19316                    self.write(text);
19317                } else {
19318                    self.write_keyword("PRECEDING");
19319                }
19320            }
19321            WindowFrameBound::BareFollowing => {
19322                if lowercase_frame {
19323                    self.write("following");
19324                } else if let Some(text) = side_text {
19325                    self.write(text);
19326                } else {
19327                    self.write_keyword("FOLLOWING");
19328                }
19329            }
19330            WindowFrameBound::Value(expr) => {
19331                // Bare numeric bound without PRECEDING/FOLLOWING
19332                self.generate_expression(expr)?;
19333            }
19334        }
19335        Ok(())
19336    }
19337
19338    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
19339        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
19340        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
19341        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
19342            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
19343            && !matches!(&interval.this, Some(Expression::Literal(_)));
19344
19345        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
19346        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
19347        if self.config.single_string_interval {
19348            if let (
19349                Some(Expression::Literal(lit)),
19350                Some(IntervalUnitSpec::Simple {
19351                    ref unit,
19352                    ref use_plural,
19353                }),
19354            ) = (&interval.this, &interval.unit)
19355            {
19356                if let Literal::String(ref val) = lit.as_ref() {
19357                    self.write_keyword("INTERVAL");
19358                    self.write_space();
19359                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
19360                    let unit_str = self.interval_unit_str(unit, effective_plural);
19361                    self.write("'");
19362                    self.write(val);
19363                    self.write(" ");
19364                    self.write(&unit_str);
19365                    self.write("'");
19366                    return Ok(());
19367                }
19368            }
19369        }
19370
19371        if !skip_interval_keyword {
19372            self.write_keyword("INTERVAL");
19373        }
19374
19375        // Generate value if present
19376        if let Some(ref value) = interval.this {
19377            if !skip_interval_keyword {
19378                self.write_space();
19379            }
19380            // If the value is a complex expression (not a literal/column/function call)
19381            // and there's a unit, wrap it in parentheses
19382            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
19383            let needs_parens = interval.unit.is_some()
19384                && matches!(
19385                    value,
19386                    Expression::Add(_)
19387                        | Expression::Sub(_)
19388                        | Expression::Mul(_)
19389                        | Expression::Div(_)
19390                        | Expression::Mod(_)
19391                        | Expression::BitwiseAnd(_)
19392                        | Expression::BitwiseOr(_)
19393                        | Expression::BitwiseXor(_)
19394                );
19395            if needs_parens {
19396                self.write("(");
19397            }
19398            self.generate_expression(value)?;
19399            if needs_parens {
19400                self.write(")");
19401            }
19402        }
19403
19404        // Generate unit if present
19405        if let Some(ref unit_spec) = interval.unit {
19406            self.write_space();
19407            self.write_interval_unit_spec(unit_spec)?;
19408        }
19409
19410        Ok(())
19411    }
19412
19413    /// Return the string representation of an interval unit
19414    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
19415        match (unit, use_plural) {
19416            (IntervalUnit::Year, false) => "YEAR",
19417            (IntervalUnit::Year, true) => "YEARS",
19418            (IntervalUnit::Quarter, false) => "QUARTER",
19419            (IntervalUnit::Quarter, true) => "QUARTERS",
19420            (IntervalUnit::Month, false) => "MONTH",
19421            (IntervalUnit::Month, true) => "MONTHS",
19422            (IntervalUnit::Week, false) => "WEEK",
19423            (IntervalUnit::Week, true) => "WEEKS",
19424            (IntervalUnit::Day, false) => "DAY",
19425            (IntervalUnit::Day, true) => "DAYS",
19426            (IntervalUnit::Hour, false) => "HOUR",
19427            (IntervalUnit::Hour, true) => "HOURS",
19428            (IntervalUnit::Minute, false) => "MINUTE",
19429            (IntervalUnit::Minute, true) => "MINUTES",
19430            (IntervalUnit::Second, false) => "SECOND",
19431            (IntervalUnit::Second, true) => "SECONDS",
19432            (IntervalUnit::Millisecond, false) => "MILLISECOND",
19433            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
19434            (IntervalUnit::Microsecond, false) => "MICROSECOND",
19435            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
19436            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
19437            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
19438        }
19439    }
19440
19441    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
19442        match unit_spec {
19443            IntervalUnitSpec::Simple { unit, use_plural } => {
19444                // If dialect doesn't allow plural forms, force singular
19445                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
19446                self.write_simple_interval_unit(unit, effective_plural);
19447            }
19448            IntervalUnitSpec::Span(span) => {
19449                self.write_simple_interval_unit(&span.this, false);
19450                self.write_space();
19451                self.write_keyword("TO");
19452                self.write_space();
19453                self.write_simple_interval_unit(&span.expression, false);
19454            }
19455            IntervalUnitSpec::ExprSpan(span) => {
19456                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
19457                self.generate_expression(&span.this)?;
19458                self.write_space();
19459                self.write_keyword("TO");
19460                self.write_space();
19461                self.generate_expression(&span.expression)?;
19462            }
19463            IntervalUnitSpec::Expr(expr) => {
19464                self.generate_expression(expr)?;
19465            }
19466        }
19467        Ok(())
19468    }
19469
19470    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
19471        // Output interval unit, respecting plural preference
19472        match (unit, use_plural) {
19473            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
19474            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
19475            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
19476            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
19477            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
19478            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
19479            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
19480            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
19481            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
19482            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
19483            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
19484            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
19485            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
19486            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
19487            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
19488            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
19489            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
19490            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
19491            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
19492            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
19493            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
19494            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
19495        }
19496    }
19497
19498    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
19499    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
19500    fn write_redshift_date_part(&mut self, expr: &Expression) {
19501        let part_str = self.extract_date_part_string(expr);
19502        if let Some(part) = part_str {
19503            let normalized = self.normalize_date_part(&part);
19504            self.write_keyword(&normalized);
19505        } else {
19506            // If we can't extract a date part string, fall back to generating the expression
19507            let _ = self.generate_expression(expr);
19508        }
19509    }
19510
19511    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
19512    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
19513    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
19514        let part_str = self.extract_date_part_string(expr);
19515        if let Some(part) = part_str {
19516            let normalized = self.normalize_date_part(&part);
19517            self.write("'");
19518            self.write(&normalized);
19519            self.write("'");
19520        } else {
19521            // If we can't extract a date part string, fall back to generating the expression
19522            let _ = self.generate_expression(expr);
19523        }
19524    }
19525
19526    /// Extract date part string from expression (handles string literals and identifiers)
19527    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
19528        match expr {
19529            Expression::Literal(lit)
19530                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
19531            {
19532                let crate::expressions::Literal::String(s) = lit.as_ref() else {
19533                    unreachable!()
19534                };
19535                Some(s.clone())
19536            }
19537            Expression::Identifier(id) => Some(id.name.clone()),
19538            Expression::Var(v) => Some(v.this.clone()),
19539            Expression::Column(col) if col.table.is_none() => {
19540                // Simple column reference without table prefix, treat as identifier
19541                Some(col.name.name.clone())
19542            }
19543            Expression::Cast(cast)
19544                if cast.format.is_none()
19545                    && cast.default.is_none()
19546                    && Self::is_string_data_type(&cast.to)
19547                    && matches!(
19548                        &cast.this,
19549                        Expression::Literal(lit)
19550                            if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
19551                    ) =>
19552            {
19553                self.extract_date_part_string(&cast.this)
19554            }
19555            _ => None,
19556        }
19557    }
19558
19559    fn classify_tsql_datetime_field(&self, field: &DateTimeField) -> TsqlDatePart {
19560        match field {
19561            DateTimeField::Year => TsqlDatePart::Native("YEAR".to_string()),
19562            DateTimeField::Month => TsqlDatePart::Native("MONTH".to_string()),
19563            DateTimeField::Day => TsqlDatePart::Native("DAY".to_string()),
19564            DateTimeField::Hour => TsqlDatePart::Native("HOUR".to_string()),
19565            DateTimeField::Minute => TsqlDatePart::Native("MINUTE".to_string()),
19566            DateTimeField::Second => TsqlDatePart::Native("SECOND".to_string()),
19567            DateTimeField::Millisecond => TsqlDatePart::Native("MILLISECOND".to_string()),
19568            DateTimeField::Microsecond => TsqlDatePart::Native("MICROSECOND".to_string()),
19569            DateTimeField::DayOfWeek => TsqlDatePart::Native("WEEKDAY".to_string()),
19570            DateTimeField::DayOfYear => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19571            DateTimeField::Week => TsqlDatePart::Native("WEEK".to_string()),
19572            DateTimeField::WeekWithModifier(modifier) => {
19573                TsqlDatePart::Unsupported(format!("WEEK({modifier})"))
19574            }
19575            DateTimeField::Quarter => TsqlDatePart::Native("QUARTER".to_string()),
19576            DateTimeField::Epoch => TsqlDatePart::Epoch,
19577            DateTimeField::TimezoneMinute => TsqlDatePart::Native("TZOFFSET".to_string()),
19578            DateTimeField::Timezone => TsqlDatePart::Unsupported("TIMEZONE".to_string()),
19579            DateTimeField::TimezoneHour => TsqlDatePart::Unsupported("TIMEZONE_HOUR".to_string()),
19580            DateTimeField::Date => TsqlDatePart::Unsupported("DATE".to_string()),
19581            DateTimeField::Time => TsqlDatePart::Unsupported("TIME".to_string()),
19582            DateTimeField::Custom(name) => self.classify_tsql_date_part_name(name),
19583        }
19584    }
19585
19586    fn classify_tsql_date_part_name(&self, part: &str) -> TsqlDatePart {
19587        let trimmed = part.trim();
19588        let upper = trimmed.to_ascii_uppercase();
19589        match upper.as_str() {
19590            "YEAR" => TsqlDatePart::Native(trimmed.to_string()),
19591            "YY" | "YYY" | "YYYY" | "YR" | "YEARS" | "YRS" => {
19592                TsqlDatePart::Native("YEAR".to_string())
19593            }
19594            "QUARTER" => TsqlDatePart::Native(trimmed.to_string()),
19595            "Q" | "QQ" | "QTR" | "QTRS" | "QUARTERS" => TsqlDatePart::Native("QUARTER".to_string()),
19596            "MONTH" => TsqlDatePart::Native(trimmed.to_string()),
19597            "MM" | "M" | "MON" | "MONS" | "MONTHS" => TsqlDatePart::Native("MONTH".to_string()),
19598            "DAYOFYEAR" => TsqlDatePart::Native(trimmed.to_string()),
19599            "DOY" | "DY" | "Y" => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19600            "DAY" => TsqlDatePart::Native(trimmed.to_string()),
19601            "D" | "DD" | "DAYS" | "DAYOFMONTH" => TsqlDatePart::Native("DAY".to_string()),
19602            "WEEK" | "W" | "WK" | "WW" | "WEEKOFYEAR" | "WOY" | "WY" | "WEEKS" => {
19603                if upper == "WEEK" {
19604                    TsqlDatePart::Native(trimmed.to_string())
19605                } else {
19606                    TsqlDatePart::Native("WEEK".to_string())
19607                }
19608            }
19609            "WEEKDAY" => TsqlDatePart::Native(trimmed.to_string()),
19610            "DAYOFWEEK" | "DOW" | "DW" => TsqlDatePart::Native("WEEKDAY".to_string()),
19611            "ISODOW" | "ISO_DOW" | "DOW_ISO" | "DW_ISO" | "ISO_DAYOFWEEK" | "ISO_WEEKDAY"
19612            | "DAYOFWEEKISO" | "DAYOFWEEK_ISO" | "WEEKDAY_ISO" => TsqlDatePart::IsoDayOfWeek,
19613            "HOUR" => TsqlDatePart::Native(trimmed.to_string()),
19614            "H" | "HH" | "HR" | "HOURS" | "HRS" => TsqlDatePart::Native("HOUR".to_string()),
19615            "MINUTE" => TsqlDatePart::Native(trimmed.to_string()),
19616            "MI" | "MIN" | "MINUTES" | "MINS" | "N" => TsqlDatePart::Native("MINUTE".to_string()),
19617            "SECOND" => TsqlDatePart::Native(trimmed.to_string()),
19618            "S" | "SEC" | "SECONDS" | "SECS" | "SS" => TsqlDatePart::Native("SECOND".to_string()),
19619            "MILLISECOND" | "MS" | "MSEC" | "MSECS" | "MSECOND" | "MSECONDS" | "MILLISEC"
19620            | "MILLISECS" | "MILLISECON" | "MILLISECONDS" => {
19621                if upper == "MILLISECOND" {
19622                    TsqlDatePart::Native(trimmed.to_string())
19623                } else {
19624                    TsqlDatePart::Native("MILLISECOND".to_string())
19625                }
19626            }
19627            "MICROSECOND" | "US" | "USEC" | "USECS" | "MICROSEC" | "MICROSECS" | "USECOND"
19628            | "USECONDS" | "MICROSECONDS" | "MCS" => {
19629                if upper == "MICROSECOND" {
19630                    TsqlDatePart::Native(trimmed.to_string())
19631                } else {
19632                    TsqlDatePart::Native("MICROSECOND".to_string())
19633                }
19634            }
19635            "NANOSECOND" | "NS" | "NSEC" | "NANOSEC" | "NSECOND" | "NSECONDS" | "NANOSECS" => {
19636                if upper == "NANOSECOND" {
19637                    TsqlDatePart::Native(trimmed.to_string())
19638                } else {
19639                    TsqlDatePart::Native("NANOSECOND".to_string())
19640                }
19641            }
19642            "TZOFFSET" => TsqlDatePart::Native(trimmed.to_string()),
19643            "TZ" | "TZM" | "TIMEZONE_MINUTE" => TsqlDatePart::Native("TZOFFSET".to_string()),
19644            "ISO_WEEK" | "ISOWEEK" | "ISOWK" | "ISOWW" | "WEEKISO" | "WEEKOFYEARISO"
19645            | "WEEKOFYEAR_ISO" | "WEEK_ISO" => {
19646                if upper == "ISO_WEEK" {
19647                    TsqlDatePart::Native(trimmed.to_string())
19648                } else {
19649                    TsqlDatePart::Native("ISO_WEEK".to_string())
19650                }
19651            }
19652            "EPOCH" | "EPOCH_SECOND" | "EPOCH_SECONDS" => TsqlDatePart::Epoch,
19653            "DECADE" | "DECADES" | "DEC" | "DECS" | "CENTURY" | "CENTURIES" | "CENT" | "CENTS"
19654            | "MILLENNIUM" | "MILLENIA" | "MIL" | "MILS" | "TIMEZONE" | "TIMEZONE_HOUR" | "TZH"
19655            | "DATE" | "TIME" => TsqlDatePart::Unsupported(upper),
19656            _ => TsqlDatePart::Unsupported(upper),
19657        }
19658    }
19659
19660    fn classify_tsql_date_trunc_field(&self, field: &DateTimeField) -> TsqlDatePart {
19661        match field {
19662            DateTimeField::Year => TsqlDatePart::Native("YEAR".to_string()),
19663            DateTimeField::Month => TsqlDatePart::Native("MONTH".to_string()),
19664            DateTimeField::Day => TsqlDatePart::Native("DAY".to_string()),
19665            DateTimeField::Hour => TsqlDatePart::Native("HOUR".to_string()),
19666            DateTimeField::Minute => TsqlDatePart::Native("MINUTE".to_string()),
19667            DateTimeField::Second => TsqlDatePart::Native("SECOND".to_string()),
19668            DateTimeField::Millisecond => TsqlDatePart::Native("MILLISECOND".to_string()),
19669            DateTimeField::Microsecond => TsqlDatePart::Native("MICROSECOND".to_string()),
19670            DateTimeField::DayOfYear => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19671            DateTimeField::Week => TsqlDatePart::Native("WEEK".to_string()),
19672            DateTimeField::WeekWithModifier(modifier) => {
19673                match modifier.to_ascii_uppercase().as_str() {
19674                    "MONDAY" | "ISO" | "ISO_WEEK" => TsqlDatePart::Native("ISO_WEEK".to_string()),
19675                    "SUNDAY" => TsqlDatePart::Native("WEEK".to_string()),
19676                    _ => TsqlDatePart::Unsupported(format!("WEEK({modifier})")),
19677                }
19678            }
19679            DateTimeField::Quarter => TsqlDatePart::Native("QUARTER".to_string()),
19680            DateTimeField::DayOfWeek => TsqlDatePart::Unsupported("WEEKDAY".to_string()),
19681            DateTimeField::Epoch => TsqlDatePart::Unsupported("EPOCH".to_string()),
19682            DateTimeField::Timezone => TsqlDatePart::Unsupported("TIMEZONE".to_string()),
19683            DateTimeField::TimezoneHour => TsqlDatePart::Unsupported("TIMEZONE_HOUR".to_string()),
19684            DateTimeField::TimezoneMinute => {
19685                TsqlDatePart::Unsupported("TIMEZONE_MINUTE".to_string())
19686            }
19687            DateTimeField::Date => TsqlDatePart::Unsupported("DATE".to_string()),
19688            DateTimeField::Time => TsqlDatePart::Unsupported("TIME".to_string()),
19689            DateTimeField::Custom(name) => self.classify_tsql_date_trunc_name(name),
19690        }
19691    }
19692
19693    fn classify_tsql_date_trunc_name(&self, part: &str) -> TsqlDatePart {
19694        let upper = part.trim().to_ascii_uppercase();
19695        match upper.as_str() {
19696            "YEAR" | "YY" | "YYY" | "YYYY" | "YR" | "YEARS" | "YRS" => {
19697                TsqlDatePart::Native("YEAR".to_string())
19698            }
19699            "QUARTER" | "Q" | "QQ" | "QTR" | "QTRS" | "QUARTERS" => {
19700                TsqlDatePart::Native("QUARTER".to_string())
19701            }
19702            "MONTH" | "MM" | "M" | "MON" | "MONS" | "MONTHS" => {
19703                TsqlDatePart::Native("MONTH".to_string())
19704            }
19705            "DAYOFYEAR" | "DOY" | "DY" | "Y" => TsqlDatePart::Native("DAYOFYEAR".to_string()),
19706            "DAY" | "D" | "DD" | "DAYS" | "DAYOFMONTH" => TsqlDatePart::Native("DAY".to_string()),
19707            "WEEK" | "W" | "WK" | "WW" | "WEEKS" | "WEEKOFYEAR" | "WOY" | "WY" => {
19708                TsqlDatePart::Native("WEEK".to_string())
19709            }
19710            "ISO_WEEK" | "ISOWEEK" | "ISOWK" | "ISOWW" | "WEEKISO" | "WEEKOFYEARISO"
19711            | "WEEKOFYEAR_ISO" | "WEEK_ISO" => TsqlDatePart::Native("ISO_WEEK".to_string()),
19712            "HOUR" | "H" | "HH" | "HR" | "HOURS" | "HRS" => {
19713                TsqlDatePart::Native("HOUR".to_string())
19714            }
19715            "MINUTE" | "MI" | "MIN" | "MINUTES" | "MINS" | "N" => {
19716                TsqlDatePart::Native("MINUTE".to_string())
19717            }
19718            "SECOND" | "S" | "SEC" | "SECONDS" | "SECS" | "SS" => {
19719                TsqlDatePart::Native("SECOND".to_string())
19720            }
19721            "MILLISECOND" | "MS" | "MSEC" | "MSECS" | "MSECOND" | "MSECONDS" | "MILLISEC"
19722            | "MILLISECS" | "MILLISECON" | "MILLISECONDS" => {
19723                TsqlDatePart::Native("MILLISECOND".to_string())
19724            }
19725            "MICROSECOND" | "US" | "USEC" | "USECS" | "MICROSEC" | "MICROSECS" | "USECOND"
19726            | "USECONDS" | "MICROSECONDS" | "MCS" => {
19727                TsqlDatePart::Native("MICROSECOND".to_string())
19728            }
19729            "WEEKDAY" | "DAYOFWEEK" | "DOW" | "DW" | "ISODOW" | "ISO_DOW" | "DOW_ISO"
19730            | "DW_ISO" | "ISO_DAYOFWEEK" | "ISO_WEEKDAY" | "DAYOFWEEKISO" | "DAYOFWEEK_ISO"
19731            | "WEEKDAY_ISO" | "NANOSECOND" | "NS" | "NSEC" | "NANOSEC" | "NSECOND" | "NSECONDS"
19732            | "NANOSECS" | "TZOFFSET" | "TZ" | "TZM" | "TIMEZONE" | "TIMEZONE_HOUR"
19733            | "TIMEZONE_MINUTE" | "TZH" | "EPOCH" | "EPOCH_SECOND" | "EPOCH_SECONDS" | "DECADE"
19734            | "DECADES" | "DEC" | "DECS" | "CENTURY" | "CENTURIES" | "CENT" | "CENTS"
19735            | "MILLENNIUM" | "MILLENIA" | "MIL" | "MILS" | "DATE" | "TIME" => {
19736                TsqlDatePart::Unsupported(upper)
19737            }
19738            _ => TsqlDatePart::Unsupported(upper),
19739        }
19740    }
19741
19742    fn generate_tsql_date_part(&mut self, part: TsqlDatePart, expr: &Expression) -> Result<()> {
19743        match part {
19744            TsqlDatePart::Native(name) => self.generate_tsql_native_datepart(&name, expr),
19745            TsqlDatePart::Epoch => self.generate_tsql_epoch_seconds(expr),
19746            TsqlDatePart::IsoDayOfWeek => self.generate_tsql_iso_day_of_week(expr),
19747            TsqlDatePart::Unsupported(name) => {
19748                self.unsupported(format!("DATEPART {name} is not supported by T-SQL/Fabric"))?;
19749                self.write_keyword("DATEPART");
19750                self.write("(");
19751                self.write_keyword(&name);
19752                self.write(", ");
19753                self.generate_expression(expr)?;
19754                self.write(")");
19755                Ok(())
19756            }
19757        }
19758    }
19759
19760    fn generate_tsql_native_datepart(&mut self, name: &str, expr: &Expression) -> Result<()> {
19761        self.write_keyword("DATEPART");
19762        self.write("(");
19763        self.write_keyword(name);
19764        self.write(", ");
19765        self.generate_expression(expr)?;
19766        self.write(")");
19767        Ok(())
19768    }
19769
19770    fn generate_tsql_epoch_seconds(&mut self, expr: &Expression) -> Result<()> {
19771        self.write_keyword("DATEDIFF");
19772        self.write("(SECOND, ");
19773        self.write_keyword("CAST");
19774        self.write("('1970-01-01' AS ");
19775        self.write_keyword("DATETIME2");
19776        self.write("), ");
19777        self.generate_expression(expr)?;
19778        self.write(")");
19779        Ok(())
19780    }
19781
19782    fn generate_tsql_iso_day_of_week(&mut self, expr: &Expression) -> Result<()> {
19783        self.write("(((DATEPART(WEEKDAY, ");
19784        self.generate_expression(expr)?;
19785        self.write(") + @@DATEFIRST - 2) % 7) + 1)");
19786        Ok(())
19787    }
19788
19789    fn generate_tsql_date_trunc(&mut self, part: TsqlDatePart, expr: &Expression) -> Result<()> {
19790        match part {
19791            TsqlDatePart::Native(name) => {
19792                self.write_keyword("DATETRUNC");
19793                self.write("(");
19794                self.write_keyword(&name);
19795                self.write(", ");
19796                self.generate_expression(expr)?;
19797                self.write(")");
19798                Ok(())
19799            }
19800            TsqlDatePart::Epoch => {
19801                self.generate_tsql_unsupported_date_trunc("EPOCH".to_string(), expr)
19802            }
19803            TsqlDatePart::IsoDayOfWeek => {
19804                self.generate_tsql_unsupported_date_trunc("ISODOW".to_string(), expr)
19805            }
19806            TsqlDatePart::Unsupported(name) => {
19807                self.generate_tsql_unsupported_date_trunc(name, expr)
19808            }
19809        }
19810    }
19811
19812    fn generate_tsql_unsupported_date_trunc(
19813        &mut self,
19814        name: String,
19815        expr: &Expression,
19816    ) -> Result<()> {
19817        self.unsupported(format!("DATETRUNC {name} is not supported by T-SQL/Fabric"))?;
19818        self.write_keyword("DATETRUNC");
19819        self.write("(");
19820        self.write_keyword(&name);
19821        self.write(", ");
19822        self.generate_expression(expr)?;
19823        self.write(")");
19824        Ok(())
19825    }
19826
19827    /// Normalize date part to uppercase singular form
19828    /// days -> DAY, months -> MONTH, etc.
19829    fn normalize_date_part(&self, part: &str) -> String {
19830        let mut buf = [0u8; 64];
19831        let lower: &str = if part.len() <= 64 {
19832            for (i, b) in part.bytes().enumerate() {
19833                buf[i] = b.to_ascii_lowercase();
19834            }
19835            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
19836        } else {
19837            return part.to_ascii_uppercase();
19838        };
19839        match lower {
19840            "day" | "days" | "d" => "DAY".to_string(),
19841            "month" | "months" | "mon" | "mons" | "mm" => "MONTH".to_string(),
19842            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
19843            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
19844            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
19845            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
19846            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
19847            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
19848            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
19849            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
19850            _ => part.to_ascii_uppercase(),
19851        }
19852    }
19853
19854    fn write_datetime_field(&mut self, field: &DateTimeField) {
19855        match field {
19856            DateTimeField::Year => self.write_keyword("YEAR"),
19857            DateTimeField::Month => self.write_keyword("MONTH"),
19858            DateTimeField::Day => self.write_keyword("DAY"),
19859            DateTimeField::Hour => self.write_keyword("HOUR"),
19860            DateTimeField::Minute => self.write_keyword("MINUTE"),
19861            DateTimeField::Second => self.write_keyword("SECOND"),
19862            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
19863            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
19864            DateTimeField::DayOfWeek => {
19865                let name = match self.config.dialect {
19866                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
19867                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
19868                    _ => "DOW",
19869                };
19870                self.write_keyword(name);
19871            }
19872            DateTimeField::DayOfYear => {
19873                let name = match self.config.dialect {
19874                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
19875                    _ => "DOY",
19876                };
19877                self.write_keyword(name);
19878            }
19879            DateTimeField::Week => self.write_keyword("WEEK"),
19880            DateTimeField::WeekWithModifier(modifier) => {
19881                self.write_keyword("WEEK");
19882                self.write("(");
19883                self.write(modifier);
19884                self.write(")");
19885            }
19886            DateTimeField::Quarter => self.write_keyword("QUARTER"),
19887            DateTimeField::Epoch => self.write_keyword("EPOCH"),
19888            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
19889            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
19890            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
19891            DateTimeField::Date => self.write_keyword("DATE"),
19892            DateTimeField::Time => self.write_keyword("TIME"),
19893            DateTimeField::Custom(name) => self.write(name),
19894        }
19895    }
19896
19897    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
19898    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
19899        match field {
19900            DateTimeField::Year => self.write("year"),
19901            DateTimeField::Month => self.write("month"),
19902            DateTimeField::Day => self.write("day"),
19903            DateTimeField::Hour => self.write("hour"),
19904            DateTimeField::Minute => self.write("minute"),
19905            DateTimeField::Second => self.write("second"),
19906            DateTimeField::Millisecond => self.write("millisecond"),
19907            DateTimeField::Microsecond => self.write("microsecond"),
19908            DateTimeField::DayOfWeek => self.write("dow"),
19909            DateTimeField::DayOfYear => self.write("doy"),
19910            DateTimeField::Week => self.write("week"),
19911            DateTimeField::WeekWithModifier(modifier) => {
19912                self.write("week(");
19913                self.write(modifier);
19914                self.write(")");
19915            }
19916            DateTimeField::Quarter => self.write("quarter"),
19917            DateTimeField::Epoch => self.write("epoch"),
19918            DateTimeField::Timezone => self.write("timezone"),
19919            DateTimeField::TimezoneHour => self.write("timezone_hour"),
19920            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
19921            DateTimeField::Date => self.write("date"),
19922            DateTimeField::Time => self.write("time"),
19923            DateTimeField::Custom(name) => self.write(name),
19924        }
19925    }
19926
19927    // Helper function generators
19928
19929    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
19930        self.write_keyword(name);
19931        self.write("(");
19932        self.generate_expression(arg)?;
19933        self.write(")");
19934        Ok(())
19935    }
19936
19937    /// Generate a unary function, using the original name if available for round-trip preservation
19938    fn generate_unary_func(
19939        &mut self,
19940        default_name: &str,
19941        f: &crate::expressions::UnaryFunc,
19942    ) -> Result<()> {
19943        let name = f.original_name.as_deref().unwrap_or(default_name);
19944        self.write_keyword(name);
19945        self.write("(");
19946        self.generate_expression(&f.this)?;
19947        self.write(")");
19948        Ok(())
19949    }
19950
19951    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
19952    fn generate_sqrt_cbrt(
19953        &mut self,
19954        f: &crate::expressions::UnaryFunc,
19955        func_name: &str,
19956        _op: &str,
19957    ) -> Result<()> {
19958        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
19959        // Always use function syntax for consistency
19960        self.write_keyword(func_name);
19961        self.write("(");
19962        self.generate_expression(&f.this)?;
19963        self.write(")");
19964        Ok(())
19965    }
19966
19967    fn generate_binary_func(
19968        &mut self,
19969        name: &str,
19970        arg1: &Expression,
19971        arg2: &Expression,
19972    ) -> Result<()> {
19973        self.write_keyword(name);
19974        self.write("(");
19975        self.generate_expression(arg1)?;
19976        self.write(", ");
19977        self.generate_expression(arg2)?;
19978        self.write(")");
19979        Ok(())
19980    }
19981
19982    /// Generate CHAR/CHR function with optional USING charset
19983    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
19984    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
19985    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
19986        // Use stored name if available, otherwise default to CHAR
19987        let func_name = f.name.as_deref().unwrap_or("CHAR");
19988        self.write_keyword(func_name);
19989        self.write("(");
19990        for (i, arg) in f.args.iter().enumerate() {
19991            if i > 0 {
19992                self.write(", ");
19993            }
19994            self.generate_expression(arg)?;
19995        }
19996        if let Some(ref charset) = f.charset {
19997            self.write(" ");
19998            self.write_keyword("USING");
19999            self.write(" ");
20000            self.write(charset);
20001        }
20002        self.write(")");
20003        Ok(())
20004    }
20005
20006    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
20007        use crate::dialects::DialectType;
20008
20009        match self.config.dialect {
20010            Some(DialectType::Teradata) => {
20011                // Teradata uses ** operator for exponentiation
20012                self.generate_expression(&f.this)?;
20013                self.write(" ** ");
20014                self.generate_expression(&f.expression)?;
20015                Ok(())
20016            }
20017            _ => {
20018                // Other dialects use POWER function
20019                self.generate_binary_func("POWER", &f.this, &f.expression)
20020            }
20021        }
20022    }
20023
20024    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
20025        self.write_func_name(name);
20026        self.write("(");
20027        for (i, arg) in args.iter().enumerate() {
20028            if i > 0 {
20029                self.write(", ");
20030            }
20031            self.generate_expression(arg)?;
20032        }
20033        self.write(")");
20034        Ok(())
20035    }
20036
20037    // String function generators
20038
20039    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
20040        self.write_keyword("CONCAT_WS");
20041        self.write("(");
20042        self.generate_expression(&f.separator)?;
20043        for expr in &f.expressions {
20044            self.write(", ");
20045            self.generate_expression(expr)?;
20046        }
20047        self.write(")");
20048        Ok(())
20049    }
20050
20051    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
20052        if let Expression::Concat(op) = expr {
20053            Self::collect_concat_operands(&op.left, out);
20054            Self::collect_concat_operands(&op.right, out);
20055        } else {
20056            out.push(expr);
20057        }
20058    }
20059
20060    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
20061        let mut operands = Vec::new();
20062        Self::collect_concat_operands(&op.left, &mut operands);
20063        Self::collect_concat_operands(&op.right, &mut operands);
20064
20065        self.write_keyword("CONCAT");
20066        self.write("(");
20067        for (i, operand) in operands.iter().enumerate() {
20068            if i > 0 {
20069                self.write(", ");
20070            }
20071            self.generate_expression(operand)?;
20072        }
20073        self.write(")");
20074        Ok(())
20075    }
20076
20077    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
20078        if let Expression::DPipe(dpipe) = expr {
20079            Self::collect_dpipe_operands(&dpipe.this, out);
20080            Self::collect_dpipe_operands(&dpipe.expression, out);
20081        } else {
20082            out.push(expr);
20083        }
20084    }
20085
20086    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
20087        let mut operands = Vec::new();
20088        Self::collect_dpipe_operands(&e.this, &mut operands);
20089        Self::collect_dpipe_operands(&e.expression, &mut operands);
20090
20091        self.write_keyword("CONCAT");
20092        self.write("(");
20093        for (i, operand) in operands.iter().enumerate() {
20094            if i > 0 {
20095                self.write(", ");
20096            }
20097            self.generate_expression(operand)?;
20098        }
20099        self.write(")");
20100        Ok(())
20101    }
20102
20103    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
20104        // Oracle and Presto-family dialects use SUBSTR; most others use SUBSTRING
20105        let use_substr = matches!(
20106            self.config.dialect,
20107            Some(
20108                DialectType::Oracle
20109                    | DialectType::Presto
20110                    | DialectType::Trino
20111                    | DialectType::Athena
20112            )
20113        );
20114        if use_substr {
20115            self.write_keyword("SUBSTR");
20116        } else {
20117            self.write_keyword("SUBSTRING");
20118        }
20119        self.write("(");
20120        self.generate_expression(&f.this)?;
20121        // PostgreSQL always uses FROM/FOR syntax
20122        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
20123        // Spark/Hive/TSQL/Fabric use comma syntax, not FROM/FOR syntax
20124        let use_comma_syntax = matches!(
20125            self.config.dialect,
20126            Some(DialectType::Spark)
20127                | Some(DialectType::Hive)
20128                | Some(DialectType::Databricks)
20129                | Some(DialectType::TSQL)
20130                | Some(DialectType::Fabric)
20131        );
20132        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
20133            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
20134            self.write_space();
20135            self.write_keyword("FROM");
20136            self.write_space();
20137            self.generate_expression(&f.start)?;
20138            if let Some(length) = &f.length {
20139                self.write_space();
20140                self.write_keyword("FOR");
20141                self.write_space();
20142                self.generate_expression(length)?;
20143            }
20144        } else {
20145            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
20146            self.write(", ");
20147            self.generate_expression(&f.start)?;
20148            if let Some(length) = &f.length {
20149                self.write(", ");
20150                self.generate_expression(length)?;
20151            }
20152        }
20153        self.write(")");
20154        Ok(())
20155    }
20156
20157    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
20158        self.write_keyword("OVERLAY");
20159        self.write("(");
20160        self.generate_expression(&f.this)?;
20161        self.write_space();
20162        self.write_keyword("PLACING");
20163        self.write_space();
20164        self.generate_expression(&f.replacement)?;
20165        self.write_space();
20166        self.write_keyword("FROM");
20167        self.write_space();
20168        self.generate_expression(&f.from)?;
20169        if let Some(length) = &f.length {
20170            self.write_space();
20171            self.write_keyword("FOR");
20172            self.write_space();
20173            self.generate_expression(length)?;
20174        }
20175        self.write(")");
20176        Ok(())
20177    }
20178
20179    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
20180        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
20181        // when no characters are specified (PostgreSQL style)
20182        if f.position_explicit && f.characters.is_none() {
20183            match f.position {
20184                TrimPosition::Leading => {
20185                    self.write_keyword("LTRIM");
20186                    self.write("(");
20187                    self.generate_expression(&f.this)?;
20188                    self.write(")");
20189                    return Ok(());
20190                }
20191                TrimPosition::Trailing => {
20192                    self.write_keyword("RTRIM");
20193                    self.write("(");
20194                    self.generate_expression(&f.this)?;
20195                    self.write(")");
20196                    return Ok(());
20197                }
20198                TrimPosition::Both => {
20199                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
20200                    // Fall through to standard TRIM handling
20201                }
20202            }
20203        }
20204
20205        self.write_keyword("TRIM");
20206        self.write("(");
20207        // When BOTH is specified without trim characters, simplify to just TRIM(str)
20208        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
20209        let force_standard = f.characters.is_some()
20210            && !f.sql_standard_syntax
20211            && matches!(
20212                self.config.dialect,
20213                Some(DialectType::Hive)
20214                    | Some(DialectType::Spark)
20215                    | Some(DialectType::Databricks)
20216                    | Some(DialectType::ClickHouse)
20217            );
20218        let use_standard = (f.sql_standard_syntax || force_standard)
20219            && !(f.position_explicit
20220                && f.characters.is_none()
20221                && matches!(f.position, TrimPosition::Both));
20222        if use_standard {
20223            // SQL standard syntax: TRIM(BOTH chars FROM str)
20224            // Only output position if it was explicitly specified
20225            if f.position_explicit {
20226                match f.position {
20227                    TrimPosition::Both => self.write_keyword("BOTH"),
20228                    TrimPosition::Leading => self.write_keyword("LEADING"),
20229                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
20230                }
20231                self.write_space();
20232            }
20233            if let Some(chars) = &f.characters {
20234                self.generate_expression(chars)?;
20235                self.write_space();
20236            }
20237            self.write_keyword("FROM");
20238            self.write_space();
20239            self.generate_expression(&f.this)?;
20240        } else {
20241            // Simple function syntax: TRIM(str) or TRIM(str, chars)
20242            self.generate_expression(&f.this)?;
20243            if let Some(chars) = &f.characters {
20244                self.write(", ");
20245                self.generate_expression(chars)?;
20246            }
20247        }
20248        self.write(")");
20249        Ok(())
20250    }
20251
20252    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
20253        self.write_keyword("REPLACE");
20254        self.write("(");
20255        self.generate_expression(&f.this)?;
20256        self.write(", ");
20257        self.generate_expression(&f.old)?;
20258        self.write(", ");
20259        self.generate_expression(&f.new)?;
20260        self.write(")");
20261        Ok(())
20262    }
20263
20264    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
20265        self.write_keyword(name);
20266        self.write("(");
20267        self.generate_expression(&f.this)?;
20268        self.write(", ");
20269        self.generate_expression(&f.length)?;
20270        self.write(")");
20271        Ok(())
20272    }
20273
20274    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
20275        self.write_keyword("REPEAT");
20276        self.write("(");
20277        self.generate_expression(&f.this)?;
20278        self.write(", ");
20279        self.generate_expression(&f.times)?;
20280        self.write(")");
20281        Ok(())
20282    }
20283
20284    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
20285        self.write_keyword(name);
20286        self.write("(");
20287        self.generate_expression(&f.this)?;
20288        self.write(", ");
20289        self.generate_expression(&f.length)?;
20290        if let Some(fill) = &f.fill {
20291            self.write(", ");
20292            self.generate_expression(fill)?;
20293        }
20294        self.write(")");
20295        Ok(())
20296    }
20297
20298    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
20299        self.write_keyword("SPLIT");
20300        self.write("(");
20301        self.generate_expression(&f.this)?;
20302        self.write(", ");
20303        self.generate_expression(&f.delimiter)?;
20304        self.write(")");
20305        Ok(())
20306    }
20307
20308    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
20309        use crate::dialects::DialectType;
20310        // PostgreSQL uses ~ operator for regex matching
20311        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
20312            self.generate_expression(&f.this)?;
20313            self.write(" ~ ");
20314            self.generate_expression(&f.pattern)?;
20315        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && f.flags.is_none()
20316        {
20317            // ClickHouse has no REGEXP_LIKE; the regex-match operator is match(haystack, pattern).
20318            self.write("match(");
20319            self.generate_expression(&f.this)?;
20320            self.write(", ");
20321            self.generate_expression(&f.pattern)?;
20322            self.write(")");
20323        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
20324            // Exasol uses REGEXP_LIKE as infix binary operator
20325            self.generate_expression(&f.this)?;
20326            self.write_keyword(" REGEXP_LIKE ");
20327            self.generate_expression(&f.pattern)?;
20328        } else if matches!(
20329            self.config.dialect,
20330            Some(DialectType::SingleStore)
20331                | Some(DialectType::Spark)
20332                | Some(DialectType::Hive)
20333                | Some(DialectType::Databricks)
20334        ) && f.flags.is_none()
20335        {
20336            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
20337            self.generate_expression(&f.this)?;
20338            self.write_keyword(" RLIKE ");
20339            self.generate_expression(&f.pattern)?;
20340        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
20341            // StarRocks uses REGEXP function syntax
20342            self.write_keyword("REGEXP");
20343            self.write("(");
20344            self.generate_expression(&f.this)?;
20345            self.write(", ");
20346            self.generate_expression(&f.pattern)?;
20347            if let Some(flags) = &f.flags {
20348                self.write(", ");
20349                self.generate_expression(flags)?;
20350            }
20351            self.write(")");
20352        } else {
20353            self.write_keyword("REGEXP_LIKE");
20354            self.write("(");
20355            self.generate_expression(&f.this)?;
20356            self.write(", ");
20357            self.generate_expression(&f.pattern)?;
20358            if let Some(flags) = &f.flags {
20359                self.write(", ");
20360                self.generate_expression(flags)?;
20361            }
20362            self.write(")");
20363        }
20364        Ok(())
20365    }
20366
20367    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
20368        self.write_keyword("REGEXP_REPLACE");
20369        self.write("(");
20370        self.generate_expression(&f.this)?;
20371        self.write(", ");
20372        self.generate_expression(&f.pattern)?;
20373        self.write(", ");
20374        self.generate_expression(&f.replacement)?;
20375        if let Some(flags) = &f.flags {
20376            self.write(", ");
20377            self.generate_expression(flags)?;
20378        }
20379        self.write(")");
20380        Ok(())
20381    }
20382
20383    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
20384        self.write_keyword("REGEXP_EXTRACT");
20385        self.write("(");
20386        self.generate_expression(&f.this)?;
20387        self.write(", ");
20388        self.generate_expression(&f.pattern)?;
20389        if let Some(group) = &f.group {
20390            self.write(", ");
20391            self.generate_expression(group)?;
20392        }
20393        self.write(")");
20394        Ok(())
20395    }
20396
20397    // Math function generators
20398
20399    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
20400        self.write_keyword("ROUND");
20401        self.write("(");
20402        self.generate_expression(&f.this)?;
20403        if let Some(decimals) = &f.decimals {
20404            self.write(", ");
20405            self.generate_expression(decimals)?;
20406        }
20407        self.write(")");
20408        Ok(())
20409    }
20410
20411    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
20412        self.write_keyword("FLOOR");
20413        self.write("(");
20414        self.generate_expression(&f.this)?;
20415        // Handle Druid-style FLOOR(time TO unit) syntax
20416        if let Some(to) = &f.to {
20417            self.write(" ");
20418            self.write_keyword("TO");
20419            self.write(" ");
20420            self.generate_expression(to)?;
20421        } else if let Some(scale) = &f.scale {
20422            self.write(", ");
20423            self.generate_expression(scale)?;
20424        }
20425        self.write(")");
20426        Ok(())
20427    }
20428
20429    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
20430        self.write_keyword("CEIL");
20431        self.write("(");
20432        self.generate_expression(&f.this)?;
20433        // Handle Druid-style CEIL(time TO unit) syntax
20434        if let Some(to) = &f.to {
20435            self.write(" ");
20436            self.write_keyword("TO");
20437            self.write(" ");
20438            self.generate_expression(to)?;
20439        } else if let Some(decimals) = &f.decimals {
20440            self.write(", ");
20441            self.generate_expression(decimals)?;
20442        }
20443        self.write(")");
20444        Ok(())
20445    }
20446
20447    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
20448        use crate::expressions::Literal;
20449
20450        if let Some(base) = &f.base {
20451            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
20452            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
20453            if self.is_log_base_none() {
20454                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
20455                {
20456                    self.write_func_name("LOG2");
20457                    self.write("(");
20458                    self.generate_expression(&f.this)?;
20459                    self.write(")");
20460                    return Ok(());
20461                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
20462                {
20463                    self.write_func_name("LOG10");
20464                    self.write("(");
20465                    self.generate_expression(&f.this)?;
20466                    self.write(")");
20467                    return Ok(());
20468                }
20469                // Other bases: fall through to LOG(base, value) — best effort
20470            }
20471
20472            self.write_func_name("LOG");
20473            self.write("(");
20474            if self.is_log_value_first() {
20475                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
20476                self.generate_expression(&f.this)?;
20477                self.write(", ");
20478                self.generate_expression(base)?;
20479            } else {
20480                // Default (PostgreSQL, etc.): LOG(base, value)
20481                self.generate_expression(base)?;
20482                self.write(", ");
20483                self.generate_expression(&f.this)?;
20484            }
20485            self.write(")");
20486        } else {
20487            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
20488            self.write_func_name("LOG");
20489            self.write("(");
20490            self.generate_expression(&f.this)?;
20491            self.write(")");
20492        }
20493        Ok(())
20494    }
20495
20496    /// Whether the target dialect uses LOG(value, base) order (value first).
20497    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
20498    fn is_log_value_first(&self) -> bool {
20499        use crate::dialects::DialectType;
20500        matches!(
20501            self.config.dialect,
20502            Some(DialectType::BigQuery)
20503                | Some(DialectType::TSQL)
20504                | Some(DialectType::Tableau)
20505                | Some(DialectType::Fabric)
20506        )
20507    }
20508
20509    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
20510    /// Presto, Trino, ClickHouse, Athena.
20511    fn is_log_base_none(&self) -> bool {
20512        use crate::dialects::DialectType;
20513        matches!(
20514            self.config.dialect,
20515            Some(DialectType::Presto)
20516                | Some(DialectType::Trino)
20517                | Some(DialectType::ClickHouse)
20518                | Some(DialectType::Athena)
20519        )
20520    }
20521
20522    // Date/time function generators
20523
20524    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
20525        self.write_keyword("CURRENT_TIME");
20526        if let Some(precision) = f.precision {
20527            self.write(&format!("({})", precision));
20528        } else if matches!(
20529            self.config.dialect,
20530            Some(crate::dialects::DialectType::MySQL)
20531                | Some(crate::dialects::DialectType::SingleStore)
20532                | Some(crate::dialects::DialectType::TiDB)
20533        ) {
20534            self.write("()");
20535        }
20536        Ok(())
20537    }
20538
20539    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
20540        use crate::dialects::DialectType;
20541
20542        // Oracle/Redshift SYSDATE handling
20543        if f.sysdate {
20544            match self.config.dialect {
20545                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
20546                    self.write_keyword("SYSDATE");
20547                    return Ok(());
20548                }
20549                Some(DialectType::Snowflake) => {
20550                    // Snowflake uses SYSDATE() function
20551                    self.write_keyword("SYSDATE");
20552                    self.write("()");
20553                    return Ok(());
20554                }
20555                _ => {
20556                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
20557                }
20558            }
20559        }
20560
20561        self.write_keyword("CURRENT_TIMESTAMP");
20562        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
20563        if let Some(precision) = f.precision {
20564            self.write(&format!("({})", precision));
20565        } else if matches!(
20566            self.config.dialect,
20567            Some(crate::dialects::DialectType::MySQL)
20568                | Some(crate::dialects::DialectType::SingleStore)
20569                | Some(crate::dialects::DialectType::TiDB)
20570                | Some(crate::dialects::DialectType::Spark)
20571                | Some(crate::dialects::DialectType::Hive)
20572                | Some(crate::dialects::DialectType::Databricks)
20573                | Some(crate::dialects::DialectType::ClickHouse)
20574                | Some(crate::dialects::DialectType::BigQuery)
20575                | Some(crate::dialects::DialectType::Snowflake)
20576                | Some(crate::dialects::DialectType::Exasol)
20577        ) {
20578            self.write("()");
20579        }
20580        Ok(())
20581    }
20582
20583    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
20584        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
20585        if self.config.dialect == Some(DialectType::Exasol) {
20586            self.write_keyword("CONVERT_TZ");
20587            self.write("(");
20588            self.generate_expression(&f.this)?;
20589            self.write(", 'UTC', ");
20590            self.generate_expression(&f.zone)?;
20591            self.write(")");
20592            return Ok(());
20593        }
20594
20595        self.generate_expression(&f.this)?;
20596        self.write_space();
20597        self.write_keyword("AT TIME ZONE");
20598        self.write_space();
20599        self.generate_expression(&f.zone)?;
20600        Ok(())
20601    }
20602
20603    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
20604        use crate::dialects::DialectType;
20605
20606        // Presto/Trino use DATE_ADD('unit', interval, date) format
20607        // with the interval cast to BIGINT when needed
20608        let is_presto_like = matches!(
20609            self.config.dialect,
20610            Some(DialectType::Presto) | Some(DialectType::Trino)
20611        );
20612
20613        if is_presto_like {
20614            self.write_keyword(name);
20615            self.write("(");
20616            // Unit as string literal
20617            self.write("'");
20618            self.write_simple_interval_unit(&f.unit, false);
20619            self.write("'");
20620            self.write(", ");
20621            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
20622            let needs_cast = !self.returns_integer_type(&f.interval);
20623            if needs_cast {
20624                self.write_keyword("CAST");
20625                self.write("(");
20626            }
20627            self.generate_expression(&f.interval)?;
20628            if needs_cast {
20629                self.write_space();
20630                self.write_keyword("AS");
20631                self.write_space();
20632                self.write_keyword("BIGINT");
20633                self.write(")");
20634            }
20635            self.write(", ");
20636            self.generate_expression(&f.this)?;
20637            self.write(")");
20638        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
20639            self.generate_expression(&f.this)?;
20640            self.write_space();
20641            if name.eq_ignore_ascii_case("DATE_SUB") {
20642                self.write("-");
20643            } else {
20644                self.write("+");
20645            }
20646            self.write_space();
20647            self.write_keyword("INTERVAL");
20648            self.write_space();
20649            self.write("'");
20650            let mut interval_gen = Generator::with_arc_config(self.config.clone());
20651            let interval_sql = interval_gen.generate(&f.interval)?;
20652            self.write(&interval_sql);
20653            self.write(" ");
20654            self.write_simple_interval_unit(&f.unit, false);
20655            self.write("'");
20656        } else {
20657            self.write_keyword(name);
20658            self.write("(");
20659            self.generate_expression(&f.this)?;
20660            self.write(", ");
20661            self.write_keyword("INTERVAL");
20662            self.write_space();
20663            self.generate_expression(&f.interval)?;
20664            self.write_space();
20665            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
20666            self.write(")");
20667        }
20668        Ok(())
20669    }
20670
20671    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
20672    /// This is a heuristic to avoid full type inference
20673    fn returns_integer_type(&self, expr: &Expression) -> bool {
20674        use crate::expressions::{DataType, Literal};
20675        match expr {
20676            // Integer literals (no decimal point)
20677            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
20678                let Literal::Number(n) = lit.as_ref() else {
20679                    unreachable!()
20680                };
20681                !n.contains('.')
20682            }
20683
20684            // FLOOR(x) returns integer if x is integer
20685            Expression::Floor(f) => self.returns_integer_type(&f.this),
20686
20687            // ROUND(x) returns integer if x is integer
20688            Expression::Round(f) => {
20689                // Only if no decimals arg or it's returning an integer
20690                f.decimals.is_none() && self.returns_integer_type(&f.this)
20691            }
20692
20693            // SIGN returns integer if input is integer
20694            Expression::Sign(f) => self.returns_integer_type(&f.this),
20695
20696            // ABS returns the same type as input
20697            Expression::Abs(f) => self.returns_integer_type(&f.this),
20698
20699            // Arithmetic operations on integers return integers
20700            Expression::Mul(op) => {
20701                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
20702            }
20703            Expression::Add(op) => {
20704                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
20705            }
20706            Expression::Sub(op) => {
20707                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
20708            }
20709            Expression::Mod(op) => self.returns_integer_type(&op.left),
20710
20711            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
20712            Expression::Cast(c) => matches!(
20713                &c.to,
20714                DataType::BigInt { .. }
20715                    | DataType::Int { .. }
20716                    | DataType::SmallInt { .. }
20717                    | DataType::TinyInt { .. }
20718            ),
20719
20720            // Negation: -x returns integer if x is integer
20721            Expression::Neg(op) => self.returns_integer_type(&op.this),
20722
20723            // Parenthesized expression
20724            Expression::Paren(p) => self.returns_integer_type(&p.this),
20725
20726            // Column references and most expressions are assumed to need casting
20727            // since we don't have full type information
20728            _ => false,
20729        }
20730    }
20731
20732    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
20733        self.write_keyword("DATEDIFF");
20734        self.write("(");
20735        if let Some(unit) = &f.unit {
20736            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
20737            self.write(", ");
20738        }
20739        if self.config.dialect == Some(DialectType::Snowflake) {
20740            self.generate_expression(&f.expression)?;
20741            self.write(", ");
20742            self.generate_expression(&f.this)?;
20743        } else {
20744            self.generate_expression(&f.this)?;
20745            self.write(", ");
20746            self.generate_expression(&f.expression)?;
20747        }
20748        self.write(")");
20749        Ok(())
20750    }
20751
20752    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
20753        if matches!(
20754            self.config.dialect,
20755            Some(DialectType::TSQL) | Some(DialectType::Fabric)
20756        ) {
20757            let date_part = self.classify_tsql_date_trunc_field(&f.unit);
20758            self.generate_tsql_date_trunc(date_part, &f.this)?;
20759            return Ok(());
20760        }
20761        if self.config.dialect == Some(DialectType::ClickHouse) {
20762            self.write("dateTrunc");
20763        } else {
20764            self.write_keyword("DATE_TRUNC");
20765        }
20766        self.write("('");
20767        self.write_datetime_field(&f.unit);
20768        self.write("', ");
20769        self.generate_expression(&f.this)?;
20770        self.write(")");
20771        Ok(())
20772    }
20773
20774    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
20775        use crate::dialects::DialectType;
20776        use crate::expressions::DateTimeField;
20777
20778        self.write_keyword("LAST_DAY");
20779        self.write("(");
20780        self.generate_expression(&f.this)?;
20781        if let Some(unit) = &f.unit {
20782            self.write(", ");
20783            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
20784            // WEEK(SUNDAY) -> WEEK
20785            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
20786                if let DateTimeField::WeekWithModifier(_) = unit {
20787                    self.write_keyword("WEEK");
20788                } else {
20789                    self.write_datetime_field(unit);
20790                }
20791            } else {
20792                self.write_datetime_field(unit);
20793            }
20794        }
20795        self.write(")");
20796        Ok(())
20797    }
20798
20799    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
20800        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
20801        if matches!(
20802            self.config.dialect,
20803            Some(DialectType::TSQL) | Some(DialectType::Fabric)
20804        ) {
20805            let date_part = self.classify_tsql_datetime_field(&f.field);
20806            self.generate_tsql_date_part(date_part, &f.this)?;
20807            return Ok(());
20808        }
20809        self.write_keyword("EXTRACT");
20810        self.write("(");
20811        // Hive/Spark use lowercase datetime fields in EXTRACT
20812        if matches!(
20813            self.config.dialect,
20814            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
20815        ) {
20816            self.write_datetime_field_lower(&f.field);
20817        } else {
20818            self.write_datetime_field(&f.field);
20819        }
20820        self.write_space();
20821        self.write_keyword("FROM");
20822        self.write_space();
20823        self.generate_expression(&f.this)?;
20824        self.write(")");
20825        Ok(())
20826    }
20827
20828    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
20829        self.write_keyword("TO_DATE");
20830        self.write("(");
20831        self.generate_expression(&f.this)?;
20832        if let Some(format) = &f.format {
20833            self.write(", ");
20834            self.generate_expression(format)?;
20835        }
20836        self.write(")");
20837        Ok(())
20838    }
20839
20840    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
20841        self.write_keyword("TO_TIMESTAMP");
20842        self.write("(");
20843        self.generate_expression(&f.this)?;
20844        if let Some(format) = &f.format {
20845            self.write(", ");
20846            self.generate_expression(format)?;
20847        }
20848        self.write(")");
20849        Ok(())
20850    }
20851
20852    // Control flow function generators
20853
20854    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
20855        use crate::dialects::DialectType;
20856
20857        // Generic mode: normalize IF to CASE WHEN
20858        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
20859            self.write_keyword("CASE WHEN");
20860            self.write_space();
20861            self.generate_expression(&f.condition)?;
20862            self.write_space();
20863            self.write_keyword("THEN");
20864            self.write_space();
20865            self.generate_expression(&f.true_value)?;
20866            if let Some(false_val) = &f.false_value {
20867                self.write_space();
20868                self.write_keyword("ELSE");
20869                self.write_space();
20870                self.generate_expression(false_val)?;
20871            }
20872            self.write_space();
20873            self.write_keyword("END");
20874            return Ok(());
20875        }
20876
20877        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
20878        if self.config.dialect == Some(DialectType::Exasol) {
20879            self.write_keyword("IF");
20880            self.write_space();
20881            self.generate_expression(&f.condition)?;
20882            self.write_space();
20883            self.write_keyword("THEN");
20884            self.write_space();
20885            self.generate_expression(&f.true_value)?;
20886            if let Some(false_val) = &f.false_value {
20887                self.write_space();
20888                self.write_keyword("ELSE");
20889                self.write_space();
20890                self.generate_expression(false_val)?;
20891            }
20892            self.write_space();
20893            self.write_keyword("ENDIF");
20894            return Ok(());
20895        }
20896
20897        // Choose function name based on target dialect
20898        let func_name = match self.config.dialect {
20899            Some(DialectType::ClickHouse) => f.original_name.as_deref().unwrap_or("IF"),
20900            Some(DialectType::Snowflake) => "IFF",
20901            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
20902            Some(DialectType::Drill) => "`IF`",
20903            _ => "IF",
20904        };
20905        self.write(func_name);
20906        self.write("(");
20907        self.generate_expression(&f.condition)?;
20908        self.write(", ");
20909        self.generate_expression(&f.true_value)?;
20910        if let Some(false_val) = &f.false_value {
20911            self.write(", ");
20912            self.generate_expression(false_val)?;
20913        }
20914        self.write(")");
20915        Ok(())
20916    }
20917
20918    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
20919        self.write_keyword("NVL2");
20920        self.write("(");
20921        self.generate_expression(&f.this)?;
20922        self.write(", ");
20923        self.generate_expression(&f.true_value)?;
20924        self.write(", ");
20925        self.generate_expression(&f.false_value)?;
20926        self.write(")");
20927        Ok(())
20928    }
20929
20930    // Typed aggregate function generators
20931
20932    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
20933        // Use normalize_functions for COUNT to respect ClickHouse case preservation
20934        let count_name = match self.config.normalize_functions {
20935            NormalizeFunctions::Upper => "COUNT".to_string(),
20936            NormalizeFunctions::Lower => "count".to_string(),
20937            NormalizeFunctions::None => f
20938                .original_name
20939                .clone()
20940                .unwrap_or_else(|| "COUNT".to_string()),
20941        };
20942        self.write(&count_name);
20943        self.write("(");
20944        if f.distinct {
20945            self.write_keyword("DISTINCT");
20946            self.write_space();
20947        }
20948        if f.star {
20949            self.write("*");
20950        } else if let Some(ref expr) = f.this {
20951            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
20952            if let Expression::Tuple(tuple) = expr {
20953                // Check if we need to transform multi-arg COUNT DISTINCT
20954                // When dialect doesn't support multi_arg_distinct, transform:
20955                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
20956                let needs_transform =
20957                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
20958
20959                if needs_transform {
20960                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
20961                    self.write_keyword("CASE");
20962                    for e in &tuple.expressions {
20963                        self.write_space();
20964                        self.write_keyword("WHEN");
20965                        self.write_space();
20966                        self.generate_expression(e)?;
20967                        self.write_space();
20968                        self.write_keyword("IS NULL THEN NULL");
20969                    }
20970                    self.write_space();
20971                    self.write_keyword("ELSE");
20972                    self.write(" (");
20973                    for (i, e) in tuple.expressions.iter().enumerate() {
20974                        if i > 0 {
20975                            self.write(", ");
20976                        }
20977                        self.generate_expression(e)?;
20978                    }
20979                    self.write(")");
20980                    self.write_space();
20981                    self.write_keyword("END");
20982                } else {
20983                    for (i, e) in tuple.expressions.iter().enumerate() {
20984                        if i > 0 {
20985                            self.write(", ");
20986                        }
20987                        self.generate_expression(e)?;
20988                    }
20989                }
20990            } else {
20991                self.generate_expression(expr)?;
20992            }
20993        }
20994        let clickhouse_ignore_nulls_outside =
20995            matches!(self.config.dialect, Some(DialectType::ClickHouse));
20996        if let Some(ignore) = f.ignore_nulls.filter(|_| !clickhouse_ignore_nulls_outside) {
20997            self.write_space();
20998            if ignore {
20999                self.write_keyword("IGNORE NULLS");
21000            } else {
21001                self.write_keyword("RESPECT NULLS");
21002            }
21003        }
21004        self.write(")");
21005        if let Some(ignore) = f.ignore_nulls.filter(|_| clickhouse_ignore_nulls_outside) {
21006            self.write_space();
21007            if ignore {
21008                self.write_keyword("IGNORE NULLS");
21009            } else {
21010                self.write_keyword("RESPECT NULLS");
21011            }
21012        }
21013        if let Some(ref filter) = f.filter {
21014            self.write_space();
21015            self.write_keyword("FILTER");
21016            self.write("(");
21017            self.write_keyword("WHERE");
21018            self.write_space();
21019            self.generate_expression(filter)?;
21020            self.write(")");
21021        }
21022        Ok(())
21023    }
21024
21025    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
21026        // Apply function name normalization based on config
21027        let func_name: Cow<'_, str> = match self.config.normalize_functions {
21028            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
21029            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
21030            NormalizeFunctions::None => {
21031                // Use the original function name from parsing if available,
21032                // otherwise fall back to lowercase of the hardcoded constant
21033                if let Some(ref original) = f.name {
21034                    Cow::Owned(original.clone())
21035                } else {
21036                    Cow::Owned(name.to_ascii_lowercase())
21037                }
21038            }
21039        };
21040        self.write(func_name.as_ref());
21041        self.write("(");
21042        if f.distinct {
21043            self.write_keyword("DISTINCT");
21044            self.write_space();
21045        }
21046        // MODE() uses a NULL placeholder internally for its zero-arg ordered-set form.
21047        // Other aggregates may legitimately receive NULL as an explicit argument.
21048        let is_zero_arg_mode =
21049            name.eq_ignore_ascii_case("MODE") && matches!(f.this, Expression::Null(_));
21050        if !is_zero_arg_mode {
21051            self.generate_expression(&f.this)?;
21052        }
21053        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
21054        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
21055        if self.config.ignore_nulls_in_func
21056            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
21057        {
21058            match f.ignore_nulls {
21059                Some(true) => {
21060                    self.write_space();
21061                    self.write_keyword("IGNORE NULLS");
21062                }
21063                Some(false) => {
21064                    self.write_space();
21065                    self.write_keyword("RESPECT NULLS");
21066                }
21067                None => {}
21068            }
21069        }
21070        // Generate HAVING MAX/MIN if present (BigQuery syntax)
21071        // e.g., ANY_VALUE(fruit HAVING MAX sold)
21072        if let Some((ref expr, is_max)) = f.having_max {
21073            self.write_space();
21074            self.write_keyword("HAVING");
21075            self.write_space();
21076            if is_max {
21077                self.write_keyword("MAX");
21078            } else {
21079                self.write_keyword("MIN");
21080            }
21081            self.write_space();
21082            self.generate_expression(expr)?;
21083        }
21084        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
21085        if !f.order_by.is_empty() {
21086            self.write_space();
21087            self.write_keyword("ORDER BY");
21088            self.write_space();
21089            for (i, ord) in f.order_by.iter().enumerate() {
21090                if i > 0 {
21091                    self.write(", ");
21092                }
21093                self.generate_ordered(ord)?;
21094            }
21095        }
21096        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
21097        if let Some(ref limit) = f.limit {
21098            self.write_space();
21099            self.write_keyword("LIMIT");
21100            self.write_space();
21101            // Check if this is a Tuple representing LIMIT offset, count
21102            if let Expression::Tuple(t) = limit.as_ref() {
21103                if t.expressions.len() == 2 {
21104                    self.generate_expression(&t.expressions[0])?;
21105                    self.write(", ");
21106                    self.generate_expression(&t.expressions[1])?;
21107                } else {
21108                    self.generate_expression(limit)?;
21109                }
21110            } else {
21111                self.generate_expression(limit)?;
21112            }
21113        }
21114        self.write(")");
21115        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
21116        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
21117        if !self.config.ignore_nulls_in_func
21118            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
21119        {
21120            match f.ignore_nulls {
21121                Some(true) => {
21122                    self.write_space();
21123                    self.write_keyword("IGNORE NULLS");
21124                }
21125                Some(false) => {
21126                    self.write_space();
21127                    self.write_keyword("RESPECT NULLS");
21128                }
21129                None => {}
21130            }
21131        }
21132        if let Some(ref filter) = f.filter {
21133            self.write_space();
21134            self.write_keyword("FILTER");
21135            self.write("(");
21136            self.write_keyword("WHERE");
21137            self.write_space();
21138            self.generate_expression(filter)?;
21139            self.write(")");
21140        }
21141        Ok(())
21142    }
21143
21144    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
21145    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
21146    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
21147        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
21148        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
21149            // Create a modified copy without ignore_nulls, add TRUE as part of the output
21150            let func_name: Cow<'_, str> = match self.config.normalize_functions {
21151                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
21152                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
21153                NormalizeFunctions::None => {
21154                    if let Some(ref original) = f.name {
21155                        Cow::Owned(original.clone())
21156                    } else {
21157                        Cow::Owned(name.to_ascii_lowercase())
21158                    }
21159                }
21160            };
21161            self.write(func_name.as_ref());
21162            self.write("(");
21163            if f.distinct {
21164                self.write_keyword("DISTINCT");
21165                self.write_space();
21166            }
21167            if !matches!(f.this, Expression::Null(_)) {
21168                self.generate_expression(&f.this)?;
21169            }
21170            self.write(", ");
21171            self.write_keyword("TRUE");
21172            self.write(")");
21173            return Ok(());
21174        }
21175        self.generate_agg_func(name, f)
21176    }
21177
21178    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
21179        self.write_keyword("GROUP_CONCAT");
21180        self.write("(");
21181        if f.distinct {
21182            self.write_keyword("DISTINCT");
21183            self.write_space();
21184        }
21185        self.generate_expression(&f.this)?;
21186        if let Some(ref order_by) = f.order_by {
21187            self.write_space();
21188            self.write_keyword("ORDER BY");
21189            self.write_space();
21190            for (i, ord) in order_by.iter().enumerate() {
21191                if i > 0 {
21192                    self.write(", ");
21193                }
21194                self.generate_ordered(ord)?;
21195            }
21196        }
21197        if let Some(ref sep) = f.separator {
21198            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
21199            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
21200            if matches!(
21201                self.config.dialect,
21202                Some(crate::dialects::DialectType::SQLite)
21203            ) {
21204                self.write(", ");
21205                self.generate_expression(sep)?;
21206            } else {
21207                self.write_space();
21208                self.write_keyword("SEPARATOR");
21209                self.write_space();
21210                self.generate_expression(sep)?;
21211            }
21212        }
21213        if let Some(ref limit) = f.limit {
21214            self.write_space();
21215            self.write_keyword("LIMIT");
21216            self.write_space();
21217            self.generate_expression(limit)?;
21218        }
21219        self.write(")");
21220        if let Some(ref filter) = f.filter {
21221            self.write_space();
21222            self.write_keyword("FILTER");
21223            self.write("(");
21224            self.write_keyword("WHERE");
21225            self.write_space();
21226            self.generate_expression(filter)?;
21227            self.write(")");
21228        }
21229        Ok(())
21230    }
21231
21232    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
21233        let uses_within_group_order = matches!(
21234            self.config.dialect,
21235            Some(crate::dialects::DialectType::TSQL | crate::dialects::DialectType::Fabric)
21236        );
21237        self.write_keyword("STRING_AGG");
21238        self.write("(");
21239        if f.distinct {
21240            self.write_keyword("DISTINCT");
21241            self.write_space();
21242        }
21243        self.generate_expression(&f.this)?;
21244        if let Some(ref separator) = f.separator {
21245            self.write(", ");
21246            self.generate_string_agg_separator(separator)?;
21247        }
21248        // TSQL/Fabric put aggregate ORDER BY in WITHIN GROUP after the closing paren.
21249        if !uses_within_group_order {
21250            if let Some(ref order_by) = f.order_by {
21251                self.write_space();
21252                self.write_keyword("ORDER BY");
21253                self.write_space();
21254                for (i, ord) in order_by.iter().enumerate() {
21255                    if i > 0 {
21256                        self.write(", ");
21257                    }
21258                    self.generate_ordered(ord)?;
21259                }
21260            }
21261        }
21262        if let Some(ref limit) = f.limit {
21263            self.write_space();
21264            self.write_keyword("LIMIT");
21265            self.write_space();
21266            self.generate_expression(limit)?;
21267        }
21268        self.write(")");
21269        if uses_within_group_order {
21270            if let Some(ref order_by) = f.order_by {
21271                self.write_space();
21272                self.write_keyword("WITHIN GROUP");
21273                self.write(" (");
21274                self.write_keyword("ORDER BY");
21275                self.write_space();
21276                for (i, ord) in order_by.iter().enumerate() {
21277                    if i > 0 {
21278                        self.write(", ");
21279                    }
21280                    self.generate_ordered(ord)?;
21281                }
21282                self.write(")");
21283            }
21284        }
21285        if let Some(ref filter) = f.filter {
21286            self.write_space();
21287            self.write_keyword("FILTER");
21288            self.write("(");
21289            self.write_keyword("WHERE");
21290            self.write_space();
21291            self.generate_expression(filter)?;
21292            self.write(")");
21293        }
21294        Ok(())
21295    }
21296
21297    fn generate_string_agg_separator(&mut self, separator: &Expression) -> Result<()> {
21298        if matches!(
21299            self.config.dialect,
21300            Some(crate::dialects::DialectType::TSQL | crate::dialects::DialectType::Fabric)
21301        ) {
21302            if let Some(inner) = Self::tsql_string_agg_literal_separator_cast_inner(separator) {
21303                return self.generate_expression(inner);
21304            }
21305        }
21306
21307        self.generate_expression(separator)
21308    }
21309
21310    fn tsql_string_agg_literal_separator_cast_inner(separator: &Expression) -> Option<&Expression> {
21311        let Expression::Cast(cast) = separator else {
21312            return None;
21313        };
21314
21315        if cast.format.is_some() || cast.default.is_some() {
21316            return None;
21317        }
21318
21319        if !Self::is_string_data_type(&cast.to) {
21320            return None;
21321        }
21322
21323        match &cast.this {
21324            Expression::Literal(literal) if literal.is_string() => Some(&cast.this),
21325            _ => None,
21326        }
21327    }
21328
21329    fn is_string_data_type(data_type: &DataType) -> bool {
21330        matches!(
21331            data_type,
21332            DataType::Char { .. }
21333                | DataType::VarChar { .. }
21334                | DataType::String { .. }
21335                | DataType::Text
21336                | DataType::TextWithLength { .. }
21337        ) || matches!(
21338            data_type,
21339            DataType::Custom { name, .. }
21340                if name.eq_ignore_ascii_case("VARCHAR")
21341                    || name.eq_ignore_ascii_case("NVARCHAR")
21342                    || name.eq_ignore_ascii_case("VARCHAR(MAX)")
21343                    || name.eq_ignore_ascii_case("NVARCHAR(MAX)")
21344        )
21345    }
21346
21347    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
21348        use crate::dialects::DialectType;
21349        let order_inside_args = matches!(self.config.dialect, Some(DialectType::DuckDB));
21350        self.write_keyword("LISTAGG");
21351        self.write("(");
21352        if f.distinct {
21353            self.write_keyword("DISTINCT");
21354            self.write_space();
21355        }
21356        self.generate_expression(&f.this)?;
21357        if let Some(ref sep) = f.separator {
21358            self.write(", ");
21359            self.generate_expression(sep)?;
21360        } else if matches!(
21361            self.config.dialect,
21362            Some(DialectType::Trino) | Some(DialectType::Presto)
21363        ) {
21364            // Trino/Presto require explicit separator; default to ','
21365            self.write(", ','");
21366        }
21367        if let Some(ref overflow) = f.on_overflow {
21368            self.write_space();
21369            self.write_keyword("ON OVERFLOW");
21370            self.write_space();
21371            match overflow {
21372                ListAggOverflow::Error => self.write_keyword("ERROR"),
21373                ListAggOverflow::Truncate { filler, with_count } => {
21374                    self.write_keyword("TRUNCATE");
21375                    if let Some(ref fill) = filler {
21376                        self.write_space();
21377                        self.generate_expression(fill)?;
21378                    }
21379                    if *with_count {
21380                        self.write_space();
21381                        self.write_keyword("WITH COUNT");
21382                    } else {
21383                        self.write_space();
21384                        self.write_keyword("WITHOUT COUNT");
21385                    }
21386                }
21387            }
21388        }
21389        if order_inside_args {
21390            if let Some(ref order_by) = f.order_by {
21391                self.write_space();
21392                self.write_keyword("ORDER BY");
21393                self.write_space();
21394                for (i, ord) in order_by.iter().enumerate() {
21395                    if i > 0 {
21396                        self.write(", ");
21397                    }
21398                    self.generate_ordered(ord)?;
21399                }
21400            }
21401        }
21402        self.write(")");
21403        if !order_inside_args {
21404            if let Some(ref order_by) = f.order_by {
21405                self.write_space();
21406                self.write_keyword("WITHIN GROUP");
21407                self.write(" (");
21408                self.write_keyword("ORDER BY");
21409                self.write_space();
21410                for (i, ord) in order_by.iter().enumerate() {
21411                    if i > 0 {
21412                        self.write(", ");
21413                    }
21414                    self.generate_ordered(ord)?;
21415                }
21416                self.write(")");
21417            }
21418        }
21419        if let Some(ref filter) = f.filter {
21420            self.write_space();
21421            self.write_keyword("FILTER");
21422            self.write("(");
21423            self.write_keyword("WHERE");
21424            self.write_space();
21425            self.generate_expression(filter)?;
21426            self.write(")");
21427        }
21428        Ok(())
21429    }
21430
21431    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
21432        self.write_keyword("SUM_IF");
21433        self.write("(");
21434        self.generate_expression(&f.this)?;
21435        self.write(", ");
21436        self.generate_expression(&f.condition)?;
21437        self.write(")");
21438        if let Some(ref filter) = f.filter {
21439            self.write_space();
21440            self.write_keyword("FILTER");
21441            self.write("(");
21442            self.write_keyword("WHERE");
21443            self.write_space();
21444            self.generate_expression(filter)?;
21445            self.write(")");
21446        }
21447        Ok(())
21448    }
21449
21450    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
21451        self.write_keyword("APPROX_PERCENTILE");
21452        self.write("(");
21453        self.generate_expression(&f.this)?;
21454        self.write(", ");
21455        self.generate_expression(&f.percentile)?;
21456        if let Some(ref acc) = f.accuracy {
21457            self.write(", ");
21458            self.generate_expression(acc)?;
21459        }
21460        self.write(")");
21461        if let Some(ref filter) = f.filter {
21462            self.write_space();
21463            self.write_keyword("FILTER");
21464            self.write("(");
21465            self.write_keyword("WHERE");
21466            self.write_space();
21467            self.generate_expression(filter)?;
21468            self.write(")");
21469        }
21470        Ok(())
21471    }
21472
21473    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
21474        self.write_keyword(name);
21475        self.write("(");
21476        self.generate_expression(&f.percentile)?;
21477        self.write(")");
21478        if let Some(ref order_by) = f.order_by {
21479            self.write_space();
21480            self.write_keyword("WITHIN GROUP");
21481            self.write(" (");
21482            self.write_keyword("ORDER BY");
21483            self.write_space();
21484            self.generate_expression(&f.this)?;
21485            for ord in order_by.iter() {
21486                if ord.desc {
21487                    self.write_space();
21488                    self.write_keyword("DESC");
21489                }
21490            }
21491            self.write(")");
21492        }
21493        if let Some(ref filter) = f.filter {
21494            self.write_space();
21495            self.write_keyword("FILTER");
21496            self.write("(");
21497            self.write_keyword("WHERE");
21498            self.write_space();
21499            self.generate_expression(filter)?;
21500            self.write(")");
21501        }
21502        Ok(())
21503    }
21504
21505    // Window function generators
21506
21507    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
21508        self.write_keyword("NTILE");
21509        self.write("(");
21510        if let Some(num_buckets) = &f.num_buckets {
21511            self.generate_expression(num_buckets)?;
21512        }
21513        if let Some(order_by) = &f.order_by {
21514            self.write_keyword(" ORDER BY ");
21515            for (i, ob) in order_by.iter().enumerate() {
21516                if i > 0 {
21517                    self.write(", ");
21518                }
21519                self.generate_ordered(ob)?;
21520            }
21521        }
21522        self.write(")");
21523        Ok(())
21524    }
21525
21526    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
21527        self.write_keyword(name);
21528        self.write("(");
21529        self.generate_expression(&f.this)?;
21530        if let Some(ref offset) = f.offset {
21531            self.write(", ");
21532            self.generate_expression(offset)?;
21533            if let Some(ref default) = f.default {
21534                self.write(", ");
21535                self.generate_expression(default)?;
21536            }
21537        }
21538        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
21539        if self.config.ignore_nulls_in_func {
21540            match f.ignore_nulls {
21541                Some(true) => {
21542                    self.write_space();
21543                    self.write_keyword("IGNORE NULLS");
21544                }
21545                Some(false) => {
21546                    self.write_space();
21547                    self.write_keyword("RESPECT NULLS");
21548                }
21549                None => {}
21550            }
21551        }
21552        self.write(")");
21553        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
21554        if !self.config.ignore_nulls_in_func {
21555            match f.ignore_nulls {
21556                Some(true) => {
21557                    self.write_space();
21558                    self.write_keyword("IGNORE NULLS");
21559                }
21560                Some(false) => {
21561                    self.write_space();
21562                    self.write_keyword("RESPECT NULLS");
21563                }
21564                None => {}
21565            }
21566        }
21567        Ok(())
21568    }
21569
21570    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
21571        self.write_keyword(name);
21572        self.write("(");
21573        self.generate_expression(&f.this)?;
21574        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
21575        if !f.order_by.is_empty() {
21576            self.write_space();
21577            self.write_keyword("ORDER BY");
21578            self.write_space();
21579            for (i, ordered) in f.order_by.iter().enumerate() {
21580                if i > 0 {
21581                    self.write(", ");
21582                }
21583                self.generate_ordered(ordered)?;
21584            }
21585        }
21586        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
21587        if self.config.ignore_nulls_in_func {
21588            match f.ignore_nulls {
21589                Some(true) => {
21590                    self.write_space();
21591                    self.write_keyword("IGNORE NULLS");
21592                }
21593                Some(false) => {
21594                    self.write_space();
21595                    self.write_keyword("RESPECT NULLS");
21596                }
21597                None => {}
21598            }
21599        }
21600        self.write(")");
21601        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
21602        if !self.config.ignore_nulls_in_func {
21603            match f.ignore_nulls {
21604                Some(true) => {
21605                    self.write_space();
21606                    self.write_keyword("IGNORE NULLS");
21607                }
21608                Some(false) => {
21609                    self.write_space();
21610                    self.write_keyword("RESPECT NULLS");
21611                }
21612                None => {}
21613            }
21614        }
21615        Ok(())
21616    }
21617
21618    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
21619    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
21620    fn generate_value_func_with_ignore_nulls_bool(
21621        &mut self,
21622        name: &str,
21623        f: &ValueFunc,
21624    ) -> Result<()> {
21625        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
21626            self.write_keyword(name);
21627            self.write("(");
21628            self.generate_expression(&f.this)?;
21629            self.write(", ");
21630            self.write_keyword("TRUE");
21631            self.write(")");
21632            return Ok(());
21633        }
21634        self.generate_value_func(name, f)
21635    }
21636
21637    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
21638        self.write_keyword("NTH_VALUE");
21639        self.write("(");
21640        self.generate_expression(&f.this)?;
21641        self.write(", ");
21642        self.generate_expression(&f.offset)?;
21643        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
21644        if self.config.ignore_nulls_in_func {
21645            match f.ignore_nulls {
21646                Some(true) => {
21647                    self.write_space();
21648                    self.write_keyword("IGNORE NULLS");
21649                }
21650                Some(false) => {
21651                    self.write_space();
21652                    self.write_keyword("RESPECT NULLS");
21653                }
21654                None => {}
21655            }
21656        }
21657        self.write(")");
21658        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
21659        if matches!(
21660            self.config.dialect,
21661            Some(crate::dialects::DialectType::Snowflake)
21662        ) {
21663            match f.from_first {
21664                Some(true) => {
21665                    self.write_space();
21666                    self.write_keyword("FROM FIRST");
21667                }
21668                Some(false) => {
21669                    self.write_space();
21670                    self.write_keyword("FROM LAST");
21671                }
21672                None => {}
21673            }
21674        }
21675        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
21676        if !self.config.ignore_nulls_in_func {
21677            match f.ignore_nulls {
21678                Some(true) => {
21679                    self.write_space();
21680                    self.write_keyword("IGNORE NULLS");
21681                }
21682                Some(false) => {
21683                    self.write_space();
21684                    self.write_keyword("RESPECT NULLS");
21685                }
21686                None => {}
21687            }
21688        }
21689        Ok(())
21690    }
21691
21692    // Additional string function generators
21693
21694    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
21695        // Standard syntax: POSITION(substr IN str)
21696        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
21697        if matches!(
21698            self.config.dialect,
21699            Some(crate::dialects::DialectType::ClickHouse)
21700        ) {
21701            self.write_keyword("POSITION");
21702            self.write("(");
21703            self.generate_expression(&f.string)?;
21704            self.write(", ");
21705            self.generate_expression(&f.substring)?;
21706            if let Some(ref start) = f.start {
21707                self.write(", ");
21708                self.generate_expression(start)?;
21709            }
21710            self.write(")");
21711            return Ok(());
21712        }
21713
21714        self.write_keyword("POSITION");
21715        self.write("(");
21716        self.generate_expression(&f.substring)?;
21717        self.write_space();
21718        self.write_keyword("IN");
21719        self.write_space();
21720        self.generate_expression(&f.string)?;
21721        if let Some(ref start) = f.start {
21722            self.write(", ");
21723            self.generate_expression(start)?;
21724        }
21725        self.write(")");
21726        Ok(())
21727    }
21728
21729    // Additional math function generators
21730
21731    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
21732        // Teradata RANDOM(lower, upper)
21733        if f.lower.is_some() || f.upper.is_some() {
21734            self.write_keyword("RANDOM");
21735            self.write("(");
21736            if let Some(ref lower) = f.lower {
21737                self.generate_expression(lower)?;
21738            }
21739            if let Some(ref upper) = f.upper {
21740                self.write(", ");
21741                self.generate_expression(upper)?;
21742            }
21743            self.write(")");
21744            return Ok(());
21745        }
21746        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
21747        let func_name = match self.config.dialect {
21748            Some(crate::dialects::DialectType::Snowflake)
21749            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
21750            _ => "RAND",
21751        };
21752        self.write_keyword(func_name);
21753        self.write("(");
21754        // DuckDB doesn't support seeded RANDOM, so skip the seed
21755        if !matches!(
21756            self.config.dialect,
21757            Some(crate::dialects::DialectType::DuckDB)
21758        ) {
21759            if let Some(ref seed) = f.seed {
21760                self.generate_expression(seed)?;
21761            }
21762        }
21763        self.write(")");
21764        Ok(())
21765    }
21766
21767    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
21768        self.write_keyword("TRUNCATE");
21769        self.write("(");
21770        self.generate_expression(&f.this)?;
21771        if let Some(ref decimals) = f.decimals {
21772            self.write(", ");
21773            self.generate_expression(decimals)?;
21774        }
21775        self.write(")");
21776        Ok(())
21777    }
21778
21779    // Control flow generators
21780
21781    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
21782        self.write_keyword("DECODE");
21783        self.write("(");
21784        self.generate_expression(&f.this)?;
21785        for (search, result) in &f.search_results {
21786            self.write(", ");
21787            self.generate_expression(search)?;
21788            self.write(", ");
21789            self.generate_expression(result)?;
21790        }
21791        if let Some(ref default) = f.default {
21792            self.write(", ");
21793            self.generate_expression(default)?;
21794        }
21795        self.write(")");
21796        Ok(())
21797    }
21798
21799    // Date/time function generators
21800
21801    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
21802        self.write_keyword(name);
21803        self.write("(");
21804        self.generate_expression(&f.this)?;
21805        self.write(", ");
21806        self.generate_expression(&f.format)?;
21807        self.write(")");
21808        Ok(())
21809    }
21810
21811    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
21812        self.write_keyword("FROM_UNIXTIME");
21813        self.write("(");
21814        self.generate_expression(&f.this)?;
21815        if let Some(ref format) = f.format {
21816            self.write(", ");
21817            self.generate_expression(format)?;
21818        }
21819        self.write(")");
21820        Ok(())
21821    }
21822
21823    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
21824        self.write_keyword("UNIX_TIMESTAMP");
21825        self.write("(");
21826        if let Some(ref expr) = f.this {
21827            self.generate_expression(expr)?;
21828            if let Some(ref format) = f.format {
21829                self.write(", ");
21830                self.generate_expression(format)?;
21831            }
21832        } else if matches!(
21833            self.config.dialect,
21834            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
21835        ) {
21836            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
21837            self.write_keyword("CURRENT_TIMESTAMP");
21838            self.write("()");
21839        }
21840        self.write(")");
21841        Ok(())
21842    }
21843
21844    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
21845        self.write_keyword("MAKE_DATE");
21846        self.write("(");
21847        self.generate_expression(&f.year)?;
21848        self.write(", ");
21849        self.generate_expression(&f.month)?;
21850        self.write(", ");
21851        self.generate_expression(&f.day)?;
21852        self.write(")");
21853        Ok(())
21854    }
21855
21856    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
21857        self.write_keyword("MAKE_TIMESTAMP");
21858        self.write("(");
21859        self.generate_expression(&f.year)?;
21860        self.write(", ");
21861        self.generate_expression(&f.month)?;
21862        self.write(", ");
21863        self.generate_expression(&f.day)?;
21864        self.write(", ");
21865        self.generate_expression(&f.hour)?;
21866        self.write(", ");
21867        self.generate_expression(&f.minute)?;
21868        self.write(", ");
21869        self.generate_expression(&f.second)?;
21870        if let Some(ref tz) = f.timezone {
21871            self.write(", ");
21872            self.generate_expression(tz)?;
21873        }
21874        self.write(")");
21875        Ok(())
21876    }
21877
21878    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
21879    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
21880        match expr {
21881            Expression::Struct(s) => {
21882                if s.fields.iter().all(|(name, _)| name.is_some()) {
21883                    Some(
21884                        s.fields
21885                            .iter()
21886                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
21887                            .collect(),
21888                    )
21889                } else {
21890                    None
21891                }
21892            }
21893            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
21894                // Check if all args are Alias (named fields)
21895                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
21896                    Some(
21897                        f.args
21898                            .iter()
21899                            .filter_map(|a| {
21900                                if let Expression::Alias(alias) = a {
21901                                    Some(alias.alias.name.clone())
21902                                } else {
21903                                    None
21904                                }
21905                            })
21906                            .collect(),
21907                    )
21908                } else {
21909                    None
21910                }
21911            }
21912            _ => None,
21913        }
21914    }
21915
21916    /// Check if a struct expression has any unnamed fields
21917    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
21918        match expr {
21919            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
21920            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
21921                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
21922            }
21923            _ => false,
21924        }
21925    }
21926
21927    /// Get the field count of a struct expression
21928    fn struct_field_count(expr: &Expression) -> usize {
21929        match expr {
21930            Expression::Struct(s) => s.fields.len(),
21931            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
21932            _ => 0,
21933        }
21934    }
21935
21936    /// Apply field names to an unnamed struct expression, producing a new expression with names
21937    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
21938        match expr {
21939            Expression::Struct(s) => {
21940                let mut new_fields = Vec::with_capacity(s.fields.len());
21941                for (i, (name, value)) in s.fields.iter().enumerate() {
21942                    if name.is_none() && i < field_names.len() {
21943                        new_fields.push((Some(field_names[i].clone()), value.clone()));
21944                    } else {
21945                        new_fields.push((name.clone(), value.clone()));
21946                    }
21947                }
21948                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
21949            }
21950            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
21951                let mut new_args = Vec::with_capacity(f.args.len());
21952                for (i, arg) in f.args.iter().enumerate() {
21953                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
21954                        // Wrap the value in an Alias with the inherited name
21955                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
21956                            this: arg.clone(),
21957                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
21958                            column_aliases: Vec::new(),
21959                            alias_explicit_as: false,
21960                            alias_keyword: None,
21961                            pre_alias_comments: Vec::new(),
21962                            trailing_comments: Vec::new(),
21963                            inferred_type: None,
21964                        })));
21965                    } else {
21966                        new_args.push(arg.clone());
21967                    }
21968                }
21969                Expression::Function(Box::new(crate::expressions::Function {
21970                    name: f.name.clone(),
21971                    args: new_args,
21972                    distinct: f.distinct,
21973                    trailing_comments: f.trailing_comments.clone(),
21974                    use_bracket_syntax: f.use_bracket_syntax,
21975                    no_parens: f.no_parens,
21976                    quoted: f.quoted,
21977                    span: None,
21978                    inferred_type: None,
21979                }))
21980            }
21981            _ => expr.clone(),
21982        }
21983    }
21984
21985    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
21986    /// This implements BigQuery's implicit field name inheritance for struct arrays.
21987    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
21988    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
21989        let first = match expressions.first() {
21990            Some(e) => e,
21991            None => return expressions.to_vec(),
21992        };
21993
21994        let field_names = match Self::extract_struct_field_names(first) {
21995            Some(names) if !names.is_empty() => names,
21996            _ => return expressions.to_vec(),
21997        };
21998
21999        let mut result = Vec::with_capacity(expressions.len());
22000        for (idx, expr) in expressions.iter().enumerate() {
22001            if idx == 0 {
22002                result.push(expr.clone());
22003                continue;
22004            }
22005            // Check if this is a struct with unnamed fields that needs name propagation
22006            if Self::struct_field_count(expr) == field_names.len()
22007                && Self::struct_has_unnamed_fields(expr)
22008            {
22009                result.push(Self::apply_struct_field_names(expr, &field_names));
22010            } else {
22011                result.push(expr.clone());
22012            }
22013        }
22014        result
22015    }
22016
22017    // Array function generators
22018
22019    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
22020        // Apply struct name inheritance for target dialects that need it
22021        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
22022        let needs_inheritance = matches!(
22023            self.config.dialect,
22024            Some(DialectType::DuckDB)
22025                | Some(DialectType::Spark)
22026                | Some(DialectType::Databricks)
22027                | Some(DialectType::Hive)
22028                | Some(DialectType::Snowflake)
22029                | Some(DialectType::Presto)
22030                | Some(DialectType::Trino)
22031        );
22032        let propagated: Vec<Expression>;
22033        let expressions = if needs_inheritance && f.expressions.len() > 1 {
22034            propagated = Self::inherit_struct_field_names(&f.expressions);
22035            &propagated
22036        } else {
22037            &f.expressions
22038        };
22039
22040        // Check if elements should be split onto multiple lines (pretty + too wide)
22041        let should_split = if self.config.pretty && !expressions.is_empty() {
22042            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
22043            for expr in expressions {
22044                let mut temp_gen = Generator::with_arc_config(self.config.clone());
22045                Arc::make_mut(&mut temp_gen.config).pretty = false;
22046                temp_gen.generate_expression(expr)?;
22047                expr_strings.push(temp_gen.output);
22048            }
22049            self.too_wide(&expr_strings)
22050        } else {
22051            false
22052        };
22053
22054        if f.bracket_notation {
22055            // For Spark/Databricks, use ARRAY(...) with parens
22056            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
22057            // For others (DuckDB, Snowflake), use bare [...]
22058            let (open, close) = match self.config.dialect {
22059                None
22060                | Some(DialectType::Generic)
22061                | Some(DialectType::Spark)
22062                | Some(DialectType::Databricks)
22063                | Some(DialectType::Hive) => {
22064                    self.write_keyword("ARRAY");
22065                    ("(", ")")
22066                }
22067                Some(DialectType::Presto)
22068                | Some(DialectType::Trino)
22069                | Some(DialectType::PostgreSQL)
22070                | Some(DialectType::Redshift)
22071                | Some(DialectType::Materialize)
22072                | Some(DialectType::RisingWave)
22073                | Some(DialectType::CockroachDB) => {
22074                    self.write_keyword("ARRAY");
22075                    ("[", "]")
22076                }
22077                _ => ("[", "]"),
22078            };
22079            self.write(open);
22080            if should_split {
22081                self.write_newline();
22082                self.indent_level += 1;
22083                for (i, expr) in expressions.iter().enumerate() {
22084                    self.write_indent();
22085                    self.generate_expression(expr)?;
22086                    if i + 1 < expressions.len() {
22087                        self.write(",");
22088                    }
22089                    self.write_newline();
22090                }
22091                self.indent_level -= 1;
22092                self.write_indent();
22093            } else {
22094                for (i, expr) in expressions.iter().enumerate() {
22095                    if i > 0 {
22096                        self.write(", ");
22097                    }
22098                    self.generate_expression(expr)?;
22099                }
22100            }
22101            self.write(close);
22102        } else {
22103            // Use LIST keyword if that was the original syntax (DuckDB)
22104            if f.use_list_keyword {
22105                self.write_keyword("LIST");
22106            } else {
22107                self.write_keyword("ARRAY");
22108            }
22109            // For Spark/Hive, always use ARRAY(...) with parens
22110            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
22111            let has_subquery = expressions
22112                .iter()
22113                .any(|e| matches!(e, Expression::Select(_)));
22114            let (open, close) = if matches!(
22115                self.config.dialect,
22116                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
22117            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
22118                && has_subquery)
22119            {
22120                ("(", ")")
22121            } else {
22122                ("[", "]")
22123            };
22124            self.write(open);
22125            if should_split {
22126                self.write_newline();
22127                self.indent_level += 1;
22128                for (i, expr) in expressions.iter().enumerate() {
22129                    self.write_indent();
22130                    self.generate_expression(expr)?;
22131                    if i + 1 < expressions.len() {
22132                        self.write(",");
22133                    }
22134                    self.write_newline();
22135                }
22136                self.indent_level -= 1;
22137                self.write_indent();
22138            } else {
22139                for (i, expr) in expressions.iter().enumerate() {
22140                    if i > 0 {
22141                        self.write(", ");
22142                    }
22143                    self.generate_expression(expr)?;
22144                }
22145            }
22146            self.write(close);
22147        }
22148        Ok(())
22149    }
22150
22151    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
22152        self.write_keyword("ARRAY_SORT");
22153        self.write("(");
22154        self.generate_expression(&f.this)?;
22155        if let Some(ref comp) = f.comparator {
22156            self.write(", ");
22157            self.generate_expression(comp)?;
22158        }
22159        self.write(")");
22160        Ok(())
22161    }
22162
22163    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
22164        self.write_keyword(name);
22165        self.write("(");
22166        self.generate_expression(&f.this)?;
22167        self.write(", ");
22168        self.generate_expression(&f.separator)?;
22169        if let Some(ref null_rep) = f.null_replacement {
22170            self.write(", ");
22171            self.generate_expression(null_rep)?;
22172        }
22173        self.write(")");
22174        Ok(())
22175    }
22176
22177    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
22178        self.write_keyword("UNNEST");
22179        self.write("(");
22180        self.generate_expression(&f.this)?;
22181        for extra in &f.expressions {
22182            self.write(", ");
22183            self.generate_expression(extra)?;
22184        }
22185        self.write(")");
22186        if f.with_ordinality {
22187            self.write_space();
22188            if self.config.unnest_with_ordinality {
22189                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
22190                self.write_keyword("WITH ORDINALITY");
22191            } else if f.offset_alias.is_some() {
22192                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
22193                // Alias (if any) comes BEFORE WITH OFFSET
22194                if let Some(ref alias) = f.alias {
22195                    self.write_keyword("AS");
22196                    self.write_space();
22197                    self.generate_identifier(alias)?;
22198                    self.write_space();
22199                }
22200                self.write_keyword("WITH OFFSET");
22201                if let Some(ref offset_alias) = f.offset_alias {
22202                    self.write_space();
22203                    self.write_keyword("AS");
22204                    self.write_space();
22205                    self.generate_identifier(offset_alias)?;
22206                }
22207            } else {
22208                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
22209                self.write_keyword("WITH OFFSET");
22210                if f.alias.is_none() {
22211                    self.write(" AS offset");
22212                }
22213            }
22214        }
22215        if let Some(ref alias) = f.alias {
22216            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
22217            let should_add_alias = if !f.with_ordinality {
22218                true
22219            } else if self.config.unnest_with_ordinality {
22220                // Presto/Trino: alias comes after WITH ORDINALITY
22221                true
22222            } else if f.offset_alias.is_some() {
22223                // BigQuery expansion: alias already handled above
22224                false
22225            } else {
22226                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
22227                true
22228            };
22229            if should_add_alias {
22230                self.write_space();
22231                self.write_keyword("AS");
22232                self.write_space();
22233                self.generate_identifier(alias)?;
22234            }
22235        }
22236        Ok(())
22237    }
22238
22239    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
22240        self.write_keyword("FILTER");
22241        self.write("(");
22242        self.generate_expression(&f.this)?;
22243        self.write(", ");
22244        self.generate_expression(&f.filter)?;
22245        self.write(")");
22246        Ok(())
22247    }
22248
22249    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
22250        self.write_keyword("TRANSFORM");
22251        self.write("(");
22252        self.generate_expression(&f.this)?;
22253        self.write(", ");
22254        self.generate_expression(&f.transform)?;
22255        self.write(")");
22256        Ok(())
22257    }
22258
22259    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
22260        self.write_keyword(name);
22261        self.write("(");
22262        self.generate_expression(&f.start)?;
22263        self.write(", ");
22264        self.generate_expression(&f.stop)?;
22265        if let Some(ref step) = f.step {
22266            self.write(", ");
22267            self.generate_expression(step)?;
22268        }
22269        self.write(")");
22270        Ok(())
22271    }
22272
22273    // Struct function generators
22274
22275    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
22276        self.write_keyword("STRUCT");
22277        self.write("(");
22278        for (i, (name, expr)) in f.fields.iter().enumerate() {
22279            if i > 0 {
22280                self.write(", ");
22281            }
22282            if let Some(ref id) = name {
22283                self.generate_identifier(id)?;
22284                self.write(" ");
22285                self.write_keyword("AS");
22286                self.write(" ");
22287            }
22288            self.generate_expression(expr)?;
22289        }
22290        self.write(")");
22291        Ok(())
22292    }
22293
22294    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
22295    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
22296        // Extract named/unnamed fields from function args
22297        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
22298        let mut names: Vec<Option<String>> = Vec::new();
22299        let mut values: Vec<&Expression> = Vec::new();
22300        let mut all_named = true;
22301
22302        for arg in &func.args {
22303            match arg {
22304                Expression::Alias(a) => {
22305                    names.push(Some(a.alias.name.clone()));
22306                    values.push(&a.this);
22307                }
22308                _ => {
22309                    names.push(None);
22310                    values.push(arg);
22311                    all_named = false;
22312                }
22313            }
22314        }
22315
22316        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
22317            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
22318            self.write("{");
22319            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
22320                if i > 0 {
22321                    self.write(", ");
22322                }
22323                if let Some(n) = name {
22324                    self.write("'");
22325                    self.write(n);
22326                    self.write("'");
22327                } else {
22328                    self.write("'_");
22329                    self.write(&i.to_string());
22330                    self.write("'");
22331                }
22332                self.write(": ");
22333                self.generate_expression(value)?;
22334            }
22335            self.write("}");
22336            return Ok(());
22337        }
22338
22339        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
22340            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
22341            self.write_keyword("OBJECT_CONSTRUCT");
22342            self.write("(");
22343            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
22344                if i > 0 {
22345                    self.write(", ");
22346                }
22347                if let Some(n) = name {
22348                    self.write("'");
22349                    self.write(n);
22350                    self.write("'");
22351                } else {
22352                    self.write("'_");
22353                    self.write(&i.to_string());
22354                    self.write("'");
22355                }
22356                self.write(", ");
22357                self.generate_expression(value)?;
22358            }
22359            self.write(")");
22360            return Ok(());
22361        }
22362
22363        if matches!(
22364            self.config.dialect,
22365            Some(DialectType::Presto) | Some(DialectType::Trino)
22366        ) {
22367            if all_named && !names.is_empty() {
22368                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
22369                // Need to infer types from values
22370                self.write_keyword("CAST");
22371                self.write("(");
22372                self.write_keyword("ROW");
22373                self.write("(");
22374                for (i, value) in values.iter().enumerate() {
22375                    if i > 0 {
22376                        self.write(", ");
22377                    }
22378                    self.generate_expression(value)?;
22379                }
22380                self.write(")");
22381                self.write(" ");
22382                self.write_keyword("AS");
22383                self.write(" ");
22384                self.write_keyword("ROW");
22385                self.write("(");
22386                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
22387                    if i > 0 {
22388                        self.write(", ");
22389                    }
22390                    if let Some(n) = name {
22391                        self.write(n);
22392                    }
22393                    self.write(" ");
22394                    let type_str = Self::infer_sql_type_for_presto(value);
22395                    self.write_keyword(&type_str);
22396                }
22397                self.write(")");
22398                self.write(")");
22399            } else {
22400                // Unnamed: ROW(values...)
22401                self.write_keyword("ROW");
22402                self.write("(");
22403                for (i, value) in values.iter().enumerate() {
22404                    if i > 0 {
22405                        self.write(", ");
22406                    }
22407                    self.generate_expression(value)?;
22408                }
22409                self.write(")");
22410            }
22411            return Ok(());
22412        }
22413
22414        // Default: ROW(values...) for other dialects
22415        self.write_keyword("ROW");
22416        self.write("(");
22417        for (i, value) in values.iter().enumerate() {
22418            if i > 0 {
22419                self.write(", ");
22420            }
22421            self.generate_expression(value)?;
22422        }
22423        self.write(")");
22424        Ok(())
22425    }
22426
22427    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
22428    fn infer_sql_type_for_presto(expr: &Expression) -> String {
22429        match expr {
22430            Expression::Literal(lit)
22431                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
22432            {
22433                "VARCHAR".to_string()
22434            }
22435            Expression::Literal(lit)
22436                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
22437            {
22438                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
22439                    unreachable!()
22440                };
22441                if n.contains('.') {
22442                    "DOUBLE".to_string()
22443                } else {
22444                    "INTEGER".to_string()
22445                }
22446            }
22447            Expression::Boolean(_) => "BOOLEAN".to_string(),
22448            Expression::Literal(lit)
22449                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
22450            {
22451                "DATE".to_string()
22452            }
22453            Expression::Literal(lit)
22454                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
22455            {
22456                "TIMESTAMP".to_string()
22457            }
22458            Expression::Literal(lit)
22459                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
22460            {
22461                "TIMESTAMP".to_string()
22462            }
22463            Expression::Array(_) | Expression::ArrayFunc(_) => {
22464                // Try to infer element type from first element
22465                "ARRAY(VARCHAR)".to_string()
22466            }
22467            // For nested structs - generate a nested ROW type by inspecting fields
22468            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
22469            Expression::Function(f) => {
22470                if f.name.eq_ignore_ascii_case("STRUCT") {
22471                    "ROW".to_string()
22472                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
22473                    "DATE".to_string()
22474                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
22475                    || f.name.eq_ignore_ascii_case("NOW")
22476                {
22477                    "TIMESTAMP".to_string()
22478                } else {
22479                    "VARCHAR".to_string()
22480                }
22481            }
22482            _ => "VARCHAR".to_string(),
22483        }
22484    }
22485
22486    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
22487        // DuckDB uses STRUCT_EXTRACT function syntax
22488        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
22489            self.write_keyword("STRUCT_EXTRACT");
22490            self.write("(");
22491            self.generate_expression(&f.this)?;
22492            self.write(", ");
22493            // Output field name as string literal
22494            self.write("'");
22495            self.write(&f.field.name);
22496            self.write("'");
22497            self.write(")");
22498            return Ok(());
22499        }
22500        self.generate_expression(&f.this)?;
22501        self.write(".");
22502        self.generate_identifier(&f.field)
22503    }
22504
22505    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
22506        if matches!(
22507            self.config.dialect,
22508            Some(DialectType::Spark | DialectType::Databricks)
22509        ) {
22510            self.write_keyword("STRUCT");
22511            self.write("(");
22512            for (i, (name, value)) in f.pairs.iter().enumerate() {
22513                if i > 0 {
22514                    self.write(", ");
22515                }
22516                self.generate_expression(value)?;
22517                self.write(" ");
22518                self.write_keyword("AS");
22519                self.write(" ");
22520                if let Expression::Literal(lit) = name {
22521                    if let Literal::String(field_name) = lit.as_ref() {
22522                        self.generate_identifier(&Identifier::new(field_name))?;
22523                    } else {
22524                        self.generate_expression(name)?;
22525                    }
22526                } else {
22527                    self.generate_expression(name)?;
22528                }
22529            }
22530            self.write(")");
22531            return Ok(());
22532        }
22533
22534        self.write_keyword("NAMED_STRUCT");
22535        self.write("(");
22536        for (i, (name, value)) in f.pairs.iter().enumerate() {
22537            if i > 0 {
22538                self.write(", ");
22539            }
22540            self.generate_expression(name)?;
22541            self.write(", ");
22542            self.generate_expression(value)?;
22543        }
22544        self.write(")");
22545        Ok(())
22546    }
22547
22548    // Map function generators
22549
22550    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
22551        if f.curly_brace_syntax {
22552            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
22553            if f.with_map_keyword {
22554                self.write_keyword("MAP");
22555                self.write(" ");
22556            }
22557            self.write("{");
22558            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
22559                if i > 0 {
22560                    self.write(", ");
22561                }
22562                self.generate_expression(key)?;
22563                self.write(": ");
22564                self.generate_expression(val)?;
22565            }
22566            self.write("}");
22567        } else {
22568            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
22569            self.write_keyword("MAP");
22570            self.write("(");
22571            self.write_keyword("ARRAY");
22572            self.write("[");
22573            for (i, key) in f.keys.iter().enumerate() {
22574                if i > 0 {
22575                    self.write(", ");
22576                }
22577                self.generate_expression(key)?;
22578            }
22579            self.write("], ");
22580            self.write_keyword("ARRAY");
22581            self.write("[");
22582            for (i, val) in f.values.iter().enumerate() {
22583                if i > 0 {
22584                    self.write(", ");
22585                }
22586                self.generate_expression(val)?;
22587            }
22588            self.write("])");
22589        }
22590        Ok(())
22591    }
22592
22593    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
22594        self.write_keyword(name);
22595        self.write("(");
22596        self.generate_expression(&f.this)?;
22597        self.write(", ");
22598        self.generate_expression(&f.transform)?;
22599        self.write(")");
22600        Ok(())
22601    }
22602
22603    // JSON function generators
22604
22605    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
22606        use crate::dialects::DialectType;
22607
22608        // Check if we should use arrow syntax (-> or ->>)
22609        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
22610
22611        if use_arrow {
22612            // Output arrow syntax: expr -> path or expr ->> path
22613            self.generate_expression(&f.this)?;
22614            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
22615                self.write(" ->> ");
22616            } else {
22617                self.write(" -> ");
22618            }
22619            self.generate_expression(&f.path)?;
22620            return Ok(());
22621        }
22622
22623        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
22624        if f.hash_arrow_syntax
22625            && matches!(
22626                self.config.dialect,
22627                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22628            )
22629        {
22630            self.generate_expression(&f.this)?;
22631            self.write(" #>> ");
22632            self.generate_expression(&f.path)?;
22633            return Ok(());
22634        }
22635
22636        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
22637        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
22638        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
22639            match name {
22640                "JSON_EXTRACT_SCALAR"
22641                | "JSON_EXTRACT_PATH_TEXT"
22642                | "JSON_EXTRACT"
22643                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
22644                _ => name,
22645            }
22646        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
22647            match name {
22648                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
22649                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
22650                _ => name,
22651            }
22652        } else {
22653            name
22654        };
22655
22656        self.write_keyword(func_name);
22657        self.write("(");
22658        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
22659        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
22660            if let Expression::Cast(ref cast) = f.this {
22661                if matches!(cast.to, crate::expressions::DataType::Json) {
22662                    self.generate_expression(&cast.this)?;
22663                } else {
22664                    self.generate_expression(&f.this)?;
22665                }
22666            } else {
22667                self.generate_expression(&f.this)?;
22668            }
22669        } else {
22670            self.generate_expression(&f.this)?;
22671        }
22672        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
22673        // decompose JSON path into separate string arguments
22674        if matches!(
22675            self.config.dialect,
22676            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22677        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
22678        {
22679            if let Expression::Literal(ref lit) = f.path {
22680                if let Literal::String(ref s) = lit.as_ref() {
22681                    let parts = Self::decompose_json_path(s);
22682                    for part in &parts {
22683                        self.write(", '");
22684                        self.write(part);
22685                        self.write("'");
22686                    }
22687                }
22688            } else {
22689                self.write(", ");
22690                self.generate_expression(&f.path)?;
22691            }
22692        } else {
22693            self.write(", ");
22694            self.generate_expression(&f.path)?;
22695        }
22696
22697        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
22698        // These go BEFORE the closing parenthesis
22699        if let Some(ref wrapper) = f.wrapper_option {
22700            self.write_space();
22701            self.write_keyword(wrapper);
22702        }
22703        if let Some(ref quotes) = f.quotes_option {
22704            self.write_space();
22705            self.write_keyword(quotes);
22706            if f.on_scalar_string {
22707                self.write_space();
22708                self.write_keyword("ON SCALAR STRING");
22709            }
22710        }
22711        if let Some(ref on_err) = f.on_error {
22712            self.write_space();
22713            self.write_keyword(on_err);
22714        }
22715        if let Some(ref ret_type) = f.returning {
22716            self.write_space();
22717            self.write_keyword("RETURNING");
22718            self.write_space();
22719            self.generate_data_type(ret_type)?;
22720        }
22721
22722        self.write(")");
22723        Ok(())
22724    }
22725
22726    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
22727    fn dialect_supports_json_arrow(&self) -> bool {
22728        use crate::dialects::DialectType;
22729        match self.config.dialect {
22730            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
22731            Some(DialectType::PostgreSQL) => true,
22732            Some(DialectType::MySQL) => true,
22733            Some(DialectType::DuckDB) => true,
22734            Some(DialectType::CockroachDB) => true,
22735            Some(DialectType::StarRocks) => true,
22736            Some(DialectType::SQLite) => true,
22737            // Other dialects use function syntax
22738            _ => false,
22739        }
22740    }
22741
22742    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
22743        use crate::dialects::DialectType;
22744
22745        // PostgreSQL uses #> operator for JSONB path extraction
22746        if matches!(
22747            self.config.dialect,
22748            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
22749        ) && name == "JSON_EXTRACT_PATH"
22750        {
22751            self.generate_expression(&f.this)?;
22752            self.write(" #> ");
22753            if f.paths.len() == 1 {
22754                self.generate_expression(&f.paths[0])?;
22755            } else {
22756                // Multiple paths: ARRAY[path1, path2, ...]
22757                self.write_keyword("ARRAY");
22758                self.write("[");
22759                for (i, path) in f.paths.iter().enumerate() {
22760                    if i > 0 {
22761                        self.write(", ");
22762                    }
22763                    self.generate_expression(path)?;
22764                }
22765                self.write("]");
22766            }
22767            return Ok(());
22768        }
22769
22770        self.write_keyword(name);
22771        self.write("(");
22772        self.generate_expression(&f.this)?;
22773        for path in &f.paths {
22774            self.write(", ");
22775            self.generate_expression(path)?;
22776        }
22777        self.write(")");
22778        Ok(())
22779    }
22780
22781    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
22782        use crate::dialects::DialectType;
22783
22784        self.write_keyword("JSON_OBJECT");
22785        self.write("(");
22786        if f.star {
22787            self.write("*");
22788        } else {
22789            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
22790            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
22791            // Also respect the json_key_value_pair_sep config
22792            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
22793                || matches!(
22794                    self.config.dialect,
22795                    Some(DialectType::BigQuery)
22796                        | Some(DialectType::MySQL)
22797                        | Some(DialectType::SQLite)
22798                );
22799
22800            for (i, (key, value)) in f.pairs.iter().enumerate() {
22801                if i > 0 {
22802                    self.write(", ");
22803                }
22804                self.generate_expression(key)?;
22805                if use_comma_syntax {
22806                    self.write(", ");
22807                } else {
22808                    self.write(": ");
22809                }
22810                self.generate_expression(value)?;
22811            }
22812        }
22813        if let Some(null_handling) = f.null_handling {
22814            self.write_space();
22815            match null_handling {
22816                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
22817                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
22818            }
22819        }
22820        if f.with_unique_keys {
22821            self.write_space();
22822            self.write_keyword("WITH UNIQUE KEYS");
22823        }
22824        if let Some(ref ret_type) = f.returning_type {
22825            self.write_space();
22826            self.write_keyword("RETURNING");
22827            self.write_space();
22828            self.generate_data_type(ret_type)?;
22829            if f.format_json {
22830                self.write_space();
22831                self.write_keyword("FORMAT JSON");
22832            }
22833            if let Some(ref enc) = f.encoding {
22834                self.write_space();
22835                self.write_keyword("ENCODING");
22836                self.write_space();
22837                self.write(enc);
22838            }
22839        }
22840        self.write(")");
22841        Ok(())
22842    }
22843
22844    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
22845        self.write_keyword(name);
22846        self.write("(");
22847        self.generate_expression(&f.this)?;
22848        for (path, value) in &f.path_values {
22849            self.write(", ");
22850            self.generate_expression(path)?;
22851            self.write(", ");
22852            self.generate_expression(value)?;
22853        }
22854        self.write(")");
22855        Ok(())
22856    }
22857
22858    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
22859        self.write_keyword("JSON_ARRAYAGG");
22860        self.write("(");
22861        self.generate_expression(&f.this)?;
22862        if let Some(ref order_by) = f.order_by {
22863            self.write_space();
22864            self.write_keyword("ORDER BY");
22865            self.write_space();
22866            for (i, ord) in order_by.iter().enumerate() {
22867                if i > 0 {
22868                    self.write(", ");
22869                }
22870                self.generate_ordered(ord)?;
22871            }
22872        }
22873        if let Some(null_handling) = f.null_handling {
22874            self.write_space();
22875            match null_handling {
22876                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
22877                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
22878            }
22879        }
22880        self.write(")");
22881        if let Some(ref filter) = f.filter {
22882            self.write_space();
22883            self.write_keyword("FILTER");
22884            self.write("(");
22885            self.write_keyword("WHERE");
22886            self.write_space();
22887            self.generate_expression(filter)?;
22888            self.write(")");
22889        }
22890        Ok(())
22891    }
22892
22893    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
22894        self.write_keyword("JSON_OBJECTAGG");
22895        self.write("(");
22896        self.generate_expression(&f.key)?;
22897        self.write(": ");
22898        self.generate_expression(&f.value)?;
22899        if let Some(null_handling) = f.null_handling {
22900            self.write_space();
22901            match null_handling {
22902                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
22903                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
22904            }
22905        }
22906        self.write(")");
22907        if let Some(ref filter) = f.filter {
22908            self.write_space();
22909            self.write_keyword("FILTER");
22910            self.write("(");
22911            self.write_keyword("WHERE");
22912            self.write_space();
22913            self.generate_expression(filter)?;
22914            self.write(")");
22915        }
22916        Ok(())
22917    }
22918
22919    // Type casting/conversion generators
22920
22921    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
22922        use crate::dialects::DialectType;
22923
22924        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
22925        if self.config.dialect == Some(DialectType::Redshift) {
22926            self.write_keyword("CAST");
22927            self.write("(");
22928            self.generate_expression(&f.this)?;
22929            self.write_space();
22930            self.write_keyword("AS");
22931            self.write_space();
22932            self.generate_data_type(&f.to)?;
22933            self.write(")");
22934            return Ok(());
22935        }
22936
22937        self.write_keyword("CONVERT");
22938        self.write("(");
22939        self.generate_data_type(&f.to)?;
22940        self.write(", ");
22941        self.generate_expression(&f.this)?;
22942        if let Some(ref style) = f.style {
22943            self.write(", ");
22944            self.generate_expression(style)?;
22945        }
22946        self.write(")");
22947        Ok(())
22948    }
22949
22950    // Additional expression generators
22951
22952    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
22953        if f.colon {
22954            // DuckDB syntax: LAMBDA x : expr
22955            self.write_keyword("LAMBDA");
22956            self.write_space();
22957            for (i, param) in f.parameters.iter().enumerate() {
22958                if i > 0 {
22959                    self.write(", ");
22960                }
22961                self.generate_identifier(param)?;
22962            }
22963            self.write(" : ");
22964        } else {
22965            // Standard syntax: x -> expr or (x, y) -> expr
22966            if f.parameters.len() == 1 {
22967                self.generate_identifier(&f.parameters[0])?;
22968            } else {
22969                self.write("(");
22970                for (i, param) in f.parameters.iter().enumerate() {
22971                    if i > 0 {
22972                        self.write(", ");
22973                    }
22974                    self.generate_identifier(param)?;
22975                }
22976                self.write(")");
22977            }
22978            self.write(" -> ");
22979        }
22980        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
22981            if let Expression::Lambda(inner) = &f.body {
22982                self.generate_lambda_with_parenthesized_single_param(inner)?;
22983                return Ok(());
22984            }
22985        }
22986
22987        self.generate_expression(&f.body)
22988    }
22989
22990    fn generate_lambda_with_parenthesized_single_param(&mut self, f: &LambdaExpr) -> Result<()> {
22991        if f.colon {
22992            return self.generate_lambda(f);
22993        }
22994
22995        self.write("(");
22996        for (i, param) in f.parameters.iter().enumerate() {
22997            if i > 0 {
22998                self.write(", ");
22999            }
23000            self.generate_identifier(param)?;
23001        }
23002        self.write(") -> ");
23003        self.generate_expression(&f.body)
23004    }
23005
23006    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
23007        self.generate_identifier(&f.name)?;
23008        match f.separator {
23009            NamedArgSeparator::DArrow => self.write(" => "),
23010            NamedArgSeparator::ColonEq => self.write(" := "),
23011            NamedArgSeparator::Eq => self.write(" = "),
23012        }
23013        self.generate_expression(&f.value)
23014    }
23015
23016    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
23017        self.write_keyword(&f.prefix);
23018        self.write(" ");
23019        self.generate_expression(&f.this)
23020    }
23021
23022    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
23023        match f.style {
23024            ParameterStyle::Question => self.write("?"),
23025            ParameterStyle::Dollar => {
23026                if let Some(idx) = f.index {
23027                    if matches!(
23028                        self.config.dialect,
23029                        Some(DialectType::TSQL) | Some(DialectType::Fabric)
23030                    ) {
23031                        self.write("@P");
23032                        self.write(&idx.to_string());
23033                    } else {
23034                        self.write("$");
23035                        self.write(&idx.to_string());
23036                    }
23037                } else {
23038                    self.write("$");
23039                    if let Some(ref name) = f.name {
23040                        // Session variable like $x or $query_id
23041                        self.write(name);
23042                    }
23043                }
23044            }
23045            ParameterStyle::DollarBrace => {
23046                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
23047                self.write("${");
23048                if let Some(ref name) = f.name {
23049                    self.write(name);
23050                }
23051                if let Some(ref expr) = f.expression {
23052                    self.write(":");
23053                    self.write(expr);
23054                }
23055                self.write("}");
23056            }
23057            ParameterStyle::Colon => {
23058                self.write(":");
23059                if let Some(idx) = f.index {
23060                    self.write(&idx.to_string());
23061                } else if let Some(ref name) = f.name {
23062                    self.write(name);
23063                }
23064            }
23065            ParameterStyle::At => {
23066                self.write("@");
23067                if let Some(ref name) = f.name {
23068                    if f.string_quoted {
23069                        self.write("'");
23070                        self.write(name);
23071                        self.write("'");
23072                    } else if f.quoted {
23073                        self.write("\"");
23074                        self.write(name);
23075                        self.write("\"");
23076                    } else {
23077                        self.write(name);
23078                    }
23079                }
23080            }
23081            ParameterStyle::DoubleAt => {
23082                self.write("@@");
23083                if let Some(ref name) = f.name {
23084                    self.write(name);
23085                }
23086            }
23087            ParameterStyle::DoubleDollar => {
23088                self.write("$$");
23089                if let Some(ref name) = f.name {
23090                    self.write(name);
23091                }
23092            }
23093            ParameterStyle::Percent => {
23094                if let Some(ref name) = f.name {
23095                    // %(name)s format
23096                    self.write("%(");
23097                    self.write(name);
23098                    self.write(")s");
23099                } else {
23100                    // %s format
23101                    self.write("%s");
23102                }
23103            }
23104            ParameterStyle::Brace => {
23105                // Spark/Databricks widget template variable: {name}
23106                // ClickHouse query parameter may include kind: {name: Type}
23107                self.write("{");
23108                if let Some(ref name) = f.name {
23109                    self.write(name);
23110                }
23111                if let Some(ref expr) = f.expression {
23112                    self.write(": ");
23113                    self.write(expr);
23114                }
23115                self.write("}");
23116            }
23117        }
23118        Ok(())
23119    }
23120
23121    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
23122        self.write("?");
23123        if let Some(idx) = f.index {
23124            self.write(&idx.to_string());
23125        }
23126        Ok(())
23127    }
23128
23129    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
23130        if f.is_block {
23131            self.write("/*");
23132            self.write(&f.text);
23133            self.write("*/");
23134        } else {
23135            self.write("--");
23136            self.write(&f.text);
23137        }
23138        Ok(())
23139    }
23140
23141    // Additional predicate generators
23142
23143    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
23144        self.generate_expression(&f.this)?;
23145        if f.not {
23146            self.write_space();
23147            self.write_keyword("NOT");
23148        }
23149        self.write_space();
23150        self.write_keyword("SIMILAR TO");
23151        self.write_space();
23152        self.generate_expression(&f.pattern)?;
23153        if let Some(ref escape) = f.escape {
23154            self.write_space();
23155            self.write_keyword("ESCAPE");
23156            self.write_space();
23157            self.generate_expression(escape)?;
23158        }
23159        Ok(())
23160    }
23161
23162    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
23163        self.generate_expression(&f.this)?;
23164        self.write_space();
23165        // Output comparison operator if present
23166        if let Some(op) = &f.op {
23167            match op {
23168                QuantifiedOp::Eq => self.write("="),
23169                QuantifiedOp::Neq => self.write("<>"),
23170                QuantifiedOp::Lt => self.write("<"),
23171                QuantifiedOp::Lte => self.write("<="),
23172                QuantifiedOp::Gt => self.write(">"),
23173                QuantifiedOp::Gte => self.write(">="),
23174            }
23175            self.write_space();
23176        }
23177        self.write_keyword(name);
23178
23179        // If the child is a Subquery, it provides its own parens — output with space
23180        if matches!(&f.subquery, Expression::Subquery(_)) {
23181            self.write_space();
23182            self.generate_expression(&f.subquery)?;
23183        } else {
23184            let is_statement = matches!(
23185                &f.subquery,
23186                Expression::Select(_)
23187                    | Expression::Union(_)
23188                    | Expression::Intersect(_)
23189                    | Expression::Except(_)
23190            );
23191            if is_statement
23192                && !self.config.quantified_no_paren_space
23193                && matches!(self.config.dialect, Some(DialectType::ClickHouse))
23194            {
23195                self.write_space();
23196            }
23197            self.write("(");
23198
23199            if self.config.pretty && is_statement {
23200                self.write_newline();
23201                self.indent_level += 1;
23202                self.write_indent();
23203            }
23204            self.generate_expression(&f.subquery)?;
23205            if self.config.pretty && is_statement {
23206                self.write_newline();
23207                self.indent_level -= 1;
23208                self.write_indent();
23209            }
23210            self.write(")");
23211        }
23212        Ok(())
23213    }
23214
23215    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
23216        // Check if this is a simple binary form (this OVERLAPS expression)
23217        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
23218            self.generate_expression(this)?;
23219            self.write_space();
23220            self.write_keyword("OVERLAPS");
23221            self.write_space();
23222            self.generate_expression(expr)?;
23223        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
23224            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
23225        {
23226            // Full ANSI form: (a, b) OVERLAPS (c, d)
23227            self.write("(");
23228            self.generate_expression(ls)?;
23229            self.write(", ");
23230            self.generate_expression(le)?;
23231            self.write(")");
23232            self.write_space();
23233            self.write_keyword("OVERLAPS");
23234            self.write_space();
23235            self.write("(");
23236            self.generate_expression(rs)?;
23237            self.write(", ");
23238            self.generate_expression(re)?;
23239            self.write(")");
23240        }
23241        Ok(())
23242    }
23243
23244    // Type conversion generators
23245
23246    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
23247        use crate::dialects::DialectType;
23248
23249        // SingleStore uses !:> syntax for try cast
23250        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
23251            self.generate_expression(&cast.this)?;
23252            self.write(" !:> ");
23253            self.generate_data_type(&cast.to)?;
23254            return Ok(());
23255        }
23256
23257        // Teradata uses TRYCAST (no underscore)
23258        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
23259            self.write_keyword("TRYCAST");
23260            self.write("(");
23261            self.generate_expression(&cast.this)?;
23262            self.write_space();
23263            self.write_keyword("AS");
23264            self.write_space();
23265            self.generate_data_type(&cast.to)?;
23266            self.write(")");
23267            return Ok(());
23268        }
23269
23270        // Dialects without TRY_CAST: generate as regular CAST
23271        let keyword = if matches!(
23272            self.config.dialect,
23273            Some(DialectType::Hive)
23274                | Some(DialectType::MySQL)
23275                | Some(DialectType::SQLite)
23276                | Some(DialectType::Oracle)
23277                | Some(DialectType::ClickHouse)
23278                | Some(DialectType::Redshift)
23279                | Some(DialectType::PostgreSQL)
23280                | Some(DialectType::StarRocks)
23281                | Some(DialectType::Doris)
23282        ) {
23283            "CAST"
23284        } else {
23285            "TRY_CAST"
23286        };
23287
23288        self.write_keyword(keyword);
23289        self.write("(");
23290        self.generate_expression(&cast.this)?;
23291        self.write_space();
23292        self.write_keyword("AS");
23293        self.write_space();
23294        self.generate_data_type(&cast.to)?;
23295
23296        // Output FORMAT clause if present
23297        if let Some(format) = &cast.format {
23298            self.write_space();
23299            self.write_keyword("FORMAT");
23300            self.write_space();
23301            self.generate_expression(format)?;
23302        }
23303
23304        self.write(")");
23305        Ok(())
23306    }
23307
23308    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
23309        self.write_keyword("SAFE_CAST");
23310        self.write("(");
23311        self.generate_expression(&cast.this)?;
23312        self.write_space();
23313        self.write_keyword("AS");
23314        self.write_space();
23315        self.generate_data_type(&cast.to)?;
23316
23317        // Output FORMAT clause if present
23318        if let Some(format) = &cast.format {
23319            self.write_space();
23320            self.write_keyword("FORMAT");
23321            self.write_space();
23322            self.generate_expression(format)?;
23323        }
23324
23325        self.write(")");
23326        Ok(())
23327    }
23328
23329    // Array/struct/map access generators
23330
23331    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
23332        // Wrap the base expression in parentheses when it uses arrow syntax (->)
23333        // which has lower precedence than bracket subscript ([]).
23334        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
23335        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
23336        if needs_parens {
23337            self.write("(");
23338        }
23339        self.generate_expression(&s.this)?;
23340        if needs_parens {
23341            self.write(")");
23342        }
23343        self.write("[");
23344        self.generate_expression(&s.index)?;
23345        self.write("]");
23346        Ok(())
23347    }
23348
23349    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
23350        self.generate_expression(&d.this)?;
23351        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
23352        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
23353        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
23354            && matches!(
23355                &d.this,
23356                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
23357            );
23358        if use_colon {
23359            self.write(":");
23360        } else {
23361            self.write(".");
23362        }
23363        self.generate_identifier(&d.field)
23364    }
23365
23366    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
23367        self.generate_expression(&m.this)?;
23368        self.write(".");
23369        // Method names after a dot should not be quoted based on reserved keywords
23370        // Only quote if explicitly marked as quoted in the AST
23371        if m.method.quoted {
23372            let q = self.config.identifier_quote;
23373            self.write(&format!("{}{}{}", q, m.method.name, q));
23374        } else {
23375            self.write(&m.method.name);
23376        }
23377        self.write("(");
23378        for (i, arg) in m.args.iter().enumerate() {
23379            if i > 0 {
23380                self.write(", ");
23381            }
23382            self.generate_expression(arg)?;
23383        }
23384        self.write(")");
23385        Ok(())
23386    }
23387
23388    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
23389        // Check if we need to wrap the inner expression in parentheses
23390        // JSON arrow expressions have lower precedence than array subscript
23391        let needs_parens = matches!(
23392            &s.this,
23393            Expression::JsonExtract(f) if f.arrow_syntax
23394        ) || matches!(
23395            &s.this,
23396            Expression::JsonExtractScalar(f) if f.arrow_syntax
23397        );
23398
23399        if needs_parens {
23400            self.write("(");
23401        }
23402        self.generate_expression(&s.this)?;
23403        if needs_parens {
23404            self.write(")");
23405        }
23406        self.write("[");
23407        if let Some(start) = &s.start {
23408            self.generate_expression(start)?;
23409        }
23410        self.write(":");
23411        if let Some(end) = &s.end {
23412            self.generate_expression(end)?;
23413        }
23414        self.write("]");
23415        Ok(())
23416    }
23417
23418    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
23419        // Generate left expression, but skip trailing comments if they're already in left_comments
23420        // to avoid duplication (comments are captured as both expr.trailing_comments
23421        // and BinaryOp.left_comments during parsing)
23422        match &op.left {
23423            Expression::Column(col) => {
23424                // Generate column with trailing comments but skip them if they're
23425                // already captured in BinaryOp.left_comments to avoid duplication
23426                if let Some(table) = &col.table {
23427                    self.generate_identifier(table)?;
23428                    self.write(".");
23429                }
23430                self.generate_identifier(&col.name)?;
23431                // Oracle-style join marker (+)
23432                if col.join_mark && self.config.supports_column_join_marks {
23433                    self.write(" (+)");
23434                }
23435                // Output column trailing comments if they're not already in left_comments
23436                if op.left_comments.is_empty() {
23437                    for comment in &col.trailing_comments {
23438                        self.write_space();
23439                        self.write_formatted_comment(comment);
23440                    }
23441                }
23442            }
23443            Expression::Add(inner_op)
23444            | Expression::Sub(inner_op)
23445            | Expression::Mul(inner_op)
23446            | Expression::Div(inner_op)
23447            | Expression::Concat(inner_op) => {
23448                // Generate binary op without its trailing comments
23449                self.generate_binary_op_no_trailing(inner_op, match &op.left {
23450                    Expression::Add(_) => "+",
23451                    Expression::Sub(_) => "-",
23452                    Expression::Mul(_) => "*",
23453                    Expression::Div(_) => "/",
23454                    Expression::Concat(_) => "||",
23455                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
23456                })?;
23457            }
23458            _ => {
23459                self.generate_expression(&op.left)?;
23460            }
23461        }
23462        // Output comments after left operand
23463        for comment in &op.left_comments {
23464            self.write_space();
23465            self.write_formatted_comment(comment);
23466        }
23467        if self.config.pretty
23468            && matches!(self.config.dialect, Some(DialectType::Snowflake))
23469            && (operator == "AND" || operator == "OR")
23470        {
23471            self.write_newline();
23472            self.write_indent();
23473            self.write_keyword(operator);
23474        } else {
23475            self.write_space();
23476            if operator.chars().all(|c| c.is_alphabetic()) {
23477                self.write_keyword(operator);
23478            } else {
23479                self.write(operator);
23480            }
23481        }
23482        // Output comments after operator (before right operand)
23483        for comment in &op.operator_comments {
23484            self.write_space();
23485            self.write_formatted_comment(comment);
23486        }
23487        self.write_space();
23488        self.generate_expression(&op.right)?;
23489        // Output trailing comments after right operand
23490        for comment in &op.trailing_comments {
23491            self.write_space();
23492            self.write_formatted_comment(comment);
23493        }
23494        Ok(())
23495    }
23496
23497    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
23498        let keyword = connector.keyword();
23499        let Some(terms) = self.flatten_connector_terms(op, connector) else {
23500            return self.generate_binary_op(op, keyword);
23501        };
23502
23503        let wrap_clickhouse_or_term = |generator: &mut Self, term: &Expression| -> Result<()> {
23504            let should_wrap = matches!(connector, ConnectorOperator::Or)
23505                && matches!(generator.config.dialect, Some(DialectType::ClickHouse))
23506                && matches!(
23507                    generator.config.source_dialect,
23508                    Some(DialectType::ClickHouse)
23509                )
23510                && matches!(term, Expression::And(_));
23511            if should_wrap {
23512                generator.write("(");
23513                generator.generate_expression(term)?;
23514                generator.write(")");
23515            } else {
23516                generator.generate_expression(term)?;
23517            }
23518            Ok(())
23519        };
23520
23521        wrap_clickhouse_or_term(self, terms[0])?;
23522        for term in terms.iter().skip(1) {
23523            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
23524                self.write_newline();
23525                self.write_indent();
23526                self.write_keyword(keyword);
23527            } else {
23528                self.write_space();
23529                self.write_keyword(keyword);
23530            }
23531            self.write_space();
23532            wrap_clickhouse_or_term(self, term)?;
23533        }
23534
23535        Ok(())
23536    }
23537
23538    fn flatten_connector_terms<'a>(
23539        &self,
23540        root: &'a BinaryOp,
23541        connector: ConnectorOperator,
23542    ) -> Option<Vec<&'a Expression>> {
23543        if !root.left_comments.is_empty()
23544            || !root.operator_comments.is_empty()
23545            || !root.trailing_comments.is_empty()
23546        {
23547            return None;
23548        }
23549
23550        let mut terms = Vec::new();
23551        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
23552
23553        while let Some(expr) = stack.pop() {
23554            match (connector, expr) {
23555                (ConnectorOperator::And, Expression::And(inner))
23556                    if inner.left_comments.is_empty()
23557                        && inner.operator_comments.is_empty()
23558                        && inner.trailing_comments.is_empty() =>
23559                {
23560                    stack.push(&inner.right);
23561                    stack.push(&inner.left);
23562                }
23563                (ConnectorOperator::Or, Expression::Or(inner))
23564                    if inner.left_comments.is_empty()
23565                        && inner.operator_comments.is_empty()
23566                        && inner.trailing_comments.is_empty() =>
23567                {
23568                    stack.push(&inner.right);
23569                    stack.push(&inner.left);
23570                }
23571                _ => terms.push(expr),
23572            }
23573        }
23574
23575        if terms.len() > 1 {
23576            Some(terms)
23577        } else {
23578            None
23579        }
23580    }
23581
23582    fn missing_closing_parens_outside_quotes(sql: &str) -> usize {
23583        let mut depth = 0usize;
23584        let mut quote: Option<char> = None;
23585        let mut chars = sql.chars().peekable();
23586
23587        while let Some(ch) = chars.next() {
23588            if let Some(quote_char) = quote {
23589                if ch == '\\' {
23590                    chars.next();
23591                } else if ch == quote_char {
23592                    if quote_char == '\'' && chars.peek() == Some(&'\'') {
23593                        chars.next();
23594                    } else {
23595                        quote = None;
23596                    }
23597                }
23598                continue;
23599            }
23600
23601            match ch {
23602                '\'' | '"' | '`' => quote = Some(ch),
23603                '(' => depth += 1,
23604                ')' => depth = depth.saturating_sub(1),
23605                _ => {}
23606            }
23607        }
23608
23609        depth
23610    }
23611
23612    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
23613    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
23614        self.generate_like_op_inner(op, operator, false)
23615    }
23616
23617    fn generate_like_op_negated(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
23618        self.generate_like_op_inner(op, operator, true)
23619    }
23620
23621    fn generate_like_op_inner(&mut self, op: &LikeOp, operator: &str, negated: bool) -> Result<()> {
23622        if negated
23623            && matches!(
23624                self.config.dialect,
23625                Some(DialectType::ClickHouse)
23626                    | Some(DialectType::DataFusion)
23627                    | Some(DialectType::TSQL)
23628                    | Some(DialectType::Fabric)
23629            )
23630        {
23631            self.write_keyword("NOT");
23632            self.write_space();
23633            return self.generate_like_op_inner(op, operator, false);
23634        }
23635
23636        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23637            if let Expression::Star(star) = &op.left {
23638                if star
23639                    .except
23640                    .as_ref()
23641                    .is_some_and(|except| !except.is_empty())
23642                {
23643                    if let Some(table) = &star.table {
23644                        self.generate_identifier(table)?;
23645                        self.write(".");
23646                    }
23647                    self.write("*");
23648                    self.write_space();
23649                    self.write_keyword(operator);
23650                    if let Some(quantifier) = &op.quantifier {
23651                        self.write_space();
23652                        self.write_keyword(quantifier);
23653                        self.write_space();
23654                    } else {
23655                        self.write_space();
23656                    }
23657                    self.generate_expression(&op.right)?;
23658                    if let Some(escape) = &op.escape {
23659                        self.write_space();
23660                        self.write_keyword("ESCAPE");
23661                        self.write_space();
23662                        self.generate_expression(escape)?;
23663                    }
23664                    if let Some(except) = &star.except {
23665                        self.write_space();
23666                        self.write_keyword("EXCEPT");
23667                        self.write(" (");
23668                        for (i, col) in except.iter().enumerate() {
23669                            if i > 0 {
23670                                self.write(", ");
23671                            }
23672                            self.generate_identifier(col)?;
23673                        }
23674                        self.write(")");
23675                    }
23676                    return Ok(());
23677                }
23678            }
23679        }
23680
23681        self.generate_expression(&op.left)?;
23682        self.write_space();
23683        if negated {
23684            self.write_keyword("NOT");
23685            self.write_space();
23686        }
23687        // Drill backtick-quotes ILIKE
23688        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
23689            self.write("`ILIKE`");
23690        } else {
23691            self.write_keyword(operator);
23692        }
23693        if let Some(quantifier) = &op.quantifier {
23694            self.write_space();
23695            self.write_keyword(quantifier);
23696            // Match Python sqlglot behavior:
23697            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
23698            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
23699            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
23700            let is_any =
23701                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
23702            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
23703                self.write_space();
23704            }
23705        } else {
23706            self.write_space();
23707        }
23708        self.generate_expression(&op.right)?;
23709        if let Some(escape) = &op.escape {
23710            self.write_space();
23711            self.write_keyword("ESCAPE");
23712            self.write_space();
23713            self.generate_expression(escape)?;
23714        }
23715        Ok(())
23716    }
23717
23718    /// Generate null-safe equality
23719    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
23720    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
23721        use crate::dialects::DialectType;
23722        self.generate_expression(&op.left)?;
23723        self.write_space();
23724        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
23725            self.write("<=>");
23726        } else {
23727            self.write_keyword("IS NOT DISTINCT FROM");
23728        }
23729        self.write_space();
23730        self.generate_expression(&op.right)?;
23731        Ok(())
23732    }
23733
23734    /// Generate IS DISTINCT FROM (null-safe inequality)
23735    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
23736        self.generate_expression(&op.left)?;
23737        self.write_space();
23738        self.write_keyword("IS DISTINCT FROM");
23739        self.write_space();
23740        self.generate_expression(&op.right)?;
23741        Ok(())
23742    }
23743
23744    /// Generate binary op without trailing comments (used when nested inside another binary op)
23745    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
23746        // Generate left expression, but skip trailing comments
23747        match &op.left {
23748            Expression::Column(col) => {
23749                if let Some(table) = &col.table {
23750                    self.generate_identifier(table)?;
23751                    self.write(".");
23752                }
23753                self.generate_identifier(&col.name)?;
23754                // Oracle-style join marker (+)
23755                if col.join_mark && self.config.supports_column_join_marks {
23756                    self.write(" (+)");
23757                }
23758            }
23759            Expression::Add(inner_op)
23760            | Expression::Sub(inner_op)
23761            | Expression::Mul(inner_op)
23762            | Expression::Div(inner_op)
23763            | Expression::Concat(inner_op) => {
23764                self.generate_binary_op_no_trailing(inner_op, match &op.left {
23765                    Expression::Add(_) => "+",
23766                    Expression::Sub(_) => "-",
23767                    Expression::Mul(_) => "*",
23768                    Expression::Div(_) => "/",
23769                    Expression::Concat(_) => "||",
23770                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
23771                })?;
23772            }
23773            _ => {
23774                self.generate_expression(&op.left)?;
23775            }
23776        }
23777        // Output left_comments
23778        for comment in &op.left_comments {
23779            self.write_space();
23780            self.write_formatted_comment(comment);
23781        }
23782        self.write_space();
23783        if operator.chars().all(|c| c.is_alphabetic()) {
23784            self.write_keyword(operator);
23785        } else {
23786            self.write(operator);
23787        }
23788        // Output operator_comments
23789        for comment in &op.operator_comments {
23790            self.write_space();
23791            self.write_formatted_comment(comment);
23792        }
23793        self.write_space();
23794        // Generate right expression, but skip trailing comments if it's a Column
23795        // (the parent's left_comments will output them)
23796        match &op.right {
23797            Expression::Column(col) => {
23798                if let Some(table) = &col.table {
23799                    self.generate_identifier(table)?;
23800                    self.write(".");
23801                }
23802                self.generate_identifier(&col.name)?;
23803                // Oracle-style join marker (+)
23804                if col.join_mark && self.config.supports_column_join_marks {
23805                    self.write(" (+)");
23806                }
23807            }
23808            _ => {
23809                self.generate_expression(&op.right)?;
23810            }
23811        }
23812        // Skip trailing_comments - parent will handle them via its left_comments
23813        Ok(())
23814    }
23815
23816    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
23817        if operator.chars().all(|c| c.is_alphabetic()) {
23818            self.write_keyword(operator);
23819            self.write_space();
23820        } else {
23821            self.write(operator);
23822            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
23823            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
23824                self.write_space();
23825            }
23826        }
23827        self.generate_expression(&op.this)
23828    }
23829
23830    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
23831        // Generic mode supports two styles for negated IN:
23832        // - Prefix: NOT a IN (...)
23833        // - Infix:  a NOT IN (...)
23834        let is_generic =
23835            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
23836        let use_prefix_not =
23837            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
23838        if use_prefix_not {
23839            self.write_keyword("NOT");
23840            self.write_space();
23841        }
23842        self.generate_expression(&in_expr.this)?;
23843        if in_expr.global {
23844            self.write_space();
23845            self.write_keyword("GLOBAL");
23846        }
23847        if in_expr.not && !use_prefix_not {
23848            self.write_space();
23849            self.write_keyword("NOT");
23850        }
23851        self.write_space();
23852        self.write_keyword("IN");
23853
23854        // BigQuery: IN UNNEST(expr)
23855        if let Some(unnest_expr) = &in_expr.unnest {
23856            self.write_space();
23857            self.write_keyword("UNNEST");
23858            self.write("(");
23859            self.generate_expression(unnest_expr)?;
23860            self.write(")");
23861            return Ok(());
23862        }
23863
23864        if let Some(query) = &in_expr.query {
23865            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
23866            // vs a subquery (col IN (SELECT ...))
23867            let is_bare = in_expr.expressions.is_empty()
23868                && !matches!(
23869                    query,
23870                    Expression::Select(_)
23871                        | Expression::Union(_)
23872                        | Expression::Intersect(_)
23873                        | Expression::Except(_)
23874                        | Expression::Subquery(_)
23875                );
23876            if is_bare {
23877                // Bare identifier: no parentheses
23878                self.write_space();
23879                self.generate_expression(query)?;
23880            } else {
23881                // Subquery: with parentheses
23882                self.write(" (");
23883                let is_statement = matches!(
23884                    query,
23885                    Expression::Select(_)
23886                        | Expression::Union(_)
23887                        | Expression::Intersect(_)
23888                        | Expression::Except(_)
23889                        | Expression::Subquery(_)
23890                );
23891                if self.config.pretty && is_statement {
23892                    self.write_newline();
23893                    self.indent_level += 1;
23894                    self.write_indent();
23895                }
23896                self.generate_expression(query)?;
23897                if self.config.pretty && is_statement {
23898                    self.write_newline();
23899                    self.indent_level -= 1;
23900                    self.write_indent();
23901                }
23902                self.write(")");
23903            }
23904        } else {
23905            // DuckDB: IN without parentheses for single expression that is NOT a literal
23906            // (array/list membership like 'red' IN tbl.flags)
23907            // ClickHouse: IN without parentheses for single non-array expressions
23908            let is_duckdb = matches!(
23909                self.config.dialect,
23910                Some(crate::dialects::DialectType::DuckDB)
23911            );
23912            let is_clickhouse = matches!(
23913                self.config.dialect,
23914                Some(crate::dialects::DialectType::ClickHouse)
23915            );
23916            let single_expr = in_expr.expressions.len() == 1;
23917            if is_clickhouse && single_expr {
23918                if let Expression::Array(arr) = &in_expr.expressions[0] {
23919                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
23920                    self.write(" (");
23921                    for (i, expr) in arr.expressions.iter().enumerate() {
23922                        if i > 0 {
23923                            self.write(", ");
23924                        }
23925                        self.generate_expression(expr)?;
23926                    }
23927                    self.write(")");
23928                } else if in_expr.is_field {
23929                    self.write_space();
23930                    self.generate_expression(&in_expr.expressions[0])?;
23931                } else {
23932                    self.write(" (");
23933                    self.generate_expression(&in_expr.expressions[0])?;
23934                    self.write(")");
23935                }
23936            } else {
23937                let is_bare_ref = single_expr
23938                    && matches!(
23939                        &in_expr.expressions[0],
23940                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
23941                    );
23942                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
23943                    // Bare field reference (no parens in source): IN identifier
23944                    // Also DuckDB: IN without parentheses for array/list membership
23945                    self.write_space();
23946                    self.generate_expression(&in_expr.expressions[0])?;
23947                } else {
23948                    // Standard IN (list)
23949                    self.write(" (");
23950                    for (i, expr) in in_expr.expressions.iter().enumerate() {
23951                        if i > 0 {
23952                            self.write(", ");
23953                        }
23954                        self.generate_expression(expr)?;
23955                    }
23956                    self.write(")");
23957                }
23958            }
23959        }
23960
23961        Ok(())
23962    }
23963
23964    fn generate_between(&mut self, between: &Between) -> Result<()> {
23965        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
23966        let use_prefix_not = between.not
23967            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
23968        if use_prefix_not {
23969            self.write_keyword("NOT");
23970            self.write_space();
23971        }
23972        self.generate_expression(&between.this)?;
23973        if between.not && !use_prefix_not {
23974            self.write_space();
23975            self.write_keyword("NOT");
23976        }
23977        self.write_space();
23978        self.write_keyword("BETWEEN");
23979        // Emit SYMMETRIC/ASYMMETRIC if present
23980        if let Some(sym) = between.symmetric {
23981            if sym {
23982                self.write(" SYMMETRIC");
23983            } else {
23984                self.write(" ASYMMETRIC");
23985            }
23986        }
23987        self.write_space();
23988        self.generate_expression(&between.low)?;
23989        self.write_space();
23990        self.write_keyword("AND");
23991        self.write_space();
23992        self.generate_expression(&between.high)
23993    }
23994
23995    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
23996        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
23997        let use_prefix_not = is_null.not
23998            && (self.config.dialect.is_none()
23999                || self.config.dialect == Some(DialectType::Generic)
24000                || is_null.postfix_form);
24001        if use_prefix_not {
24002            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
24003            self.write_keyword("NOT");
24004            self.write_space();
24005            self.generate_expression(&is_null.this)?;
24006            self.write_space();
24007            self.write_keyword("IS");
24008            self.write_space();
24009            self.write_keyword("NULL");
24010        } else {
24011            self.generate_expression(&is_null.this)?;
24012            self.write_space();
24013            self.write_keyword("IS");
24014            if is_null.not {
24015                self.write_space();
24016                self.write_keyword("NOT");
24017            }
24018            self.write_space();
24019            self.write_keyword("NULL");
24020        }
24021        Ok(())
24022    }
24023
24024    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
24025        self.generate_expression(&is_true.this)?;
24026        self.write_space();
24027        self.write_keyword("IS");
24028        if is_true.not {
24029            self.write_space();
24030            self.write_keyword("NOT");
24031        }
24032        self.write_space();
24033        self.write_keyword("TRUE");
24034        Ok(())
24035    }
24036
24037    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
24038        self.generate_expression(&is_false.this)?;
24039        self.write_space();
24040        self.write_keyword("IS");
24041        if is_false.not {
24042            self.write_space();
24043            self.write_keyword("NOT");
24044        }
24045        self.write_space();
24046        self.write_keyword("FALSE");
24047        Ok(())
24048    }
24049
24050    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
24051        self.generate_expression(&is_json.this)?;
24052        self.write_space();
24053        self.write_keyword("IS");
24054        if is_json.negated {
24055            self.write_space();
24056            self.write_keyword("NOT");
24057        }
24058        self.write_space();
24059        self.write_keyword("JSON");
24060
24061        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
24062        if let Some(ref json_type) = is_json.json_type {
24063            self.write_space();
24064            self.write_keyword(json_type);
24065        }
24066
24067        // Output key uniqueness constraint if specified
24068        match &is_json.unique_keys {
24069            Some(JsonUniqueKeys::With) => {
24070                self.write_space();
24071                self.write_keyword("WITH UNIQUE KEYS");
24072            }
24073            Some(JsonUniqueKeys::Without) => {
24074                self.write_space();
24075                self.write_keyword("WITHOUT UNIQUE KEYS");
24076            }
24077            Some(JsonUniqueKeys::Shorthand) => {
24078                self.write_space();
24079                self.write_keyword("UNIQUE KEYS");
24080            }
24081            None => {}
24082        }
24083
24084        Ok(())
24085    }
24086
24087    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
24088        self.generate_expression(&is_expr.left)?;
24089        self.write_space();
24090        self.write_keyword("IS");
24091        self.write_space();
24092        self.generate_expression(&is_expr.right)
24093    }
24094
24095    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
24096        if exists.not {
24097            self.write_keyword("NOT");
24098            self.write_space();
24099        }
24100        self.write_keyword("EXISTS");
24101        self.write("(");
24102        let is_statement = matches!(
24103            &exists.this,
24104            Expression::Select(_)
24105                | Expression::Union(_)
24106                | Expression::Intersect(_)
24107                | Expression::Except(_)
24108        );
24109        if self.config.pretty && is_statement {
24110            self.write_newline();
24111            self.indent_level += 1;
24112            self.write_indent();
24113            self.generate_expression(&exists.this)?;
24114            self.write_newline();
24115            self.indent_level -= 1;
24116            self.write_indent();
24117            self.write(")");
24118        } else {
24119            self.generate_expression(&exists.this)?;
24120            self.write(")");
24121        }
24122        Ok(())
24123    }
24124
24125    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
24126        self.generate_expression(&op.left)?;
24127        self.write_space();
24128        self.write_keyword("MEMBER OF");
24129        self.write("(");
24130        self.generate_expression(&op.right)?;
24131        self.write(")");
24132        Ok(())
24133    }
24134
24135    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
24136        if subquery.lateral {
24137            self.write_keyword("LATERAL");
24138            self.write_space();
24139        }
24140
24141        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
24142        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
24143        // to carry the LIMIT modifier without adding more parens
24144        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
24145            matches!(
24146                &p.this,
24147                Expression::Select(_)
24148                    | Expression::Union(_)
24149                    | Expression::Intersect(_)
24150                    | Expression::Except(_)
24151                    | Expression::Subquery(_)
24152            )
24153        } else {
24154            false
24155        };
24156
24157        // Check if inner expression is a statement for pretty formatting
24158        let is_statement = matches!(
24159            &subquery.this,
24160            Expression::Select(_)
24161                | Expression::Union(_)
24162                | Expression::Intersect(_)
24163                | Expression::Except(_)
24164                | Expression::Merge(_)
24165        );
24166
24167        if !skip_outer_parens {
24168            self.write("(");
24169            if self.config.pretty && is_statement {
24170                self.write_newline();
24171                self.indent_level += 1;
24172                self.write_indent();
24173            }
24174        }
24175        self.generate_expression(&subquery.this)?;
24176
24177        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
24178        if subquery.modifiers_inside {
24179            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
24180            if let Some(order_by) = &subquery.order_by {
24181                self.write_space();
24182                self.write_keyword("ORDER BY");
24183                self.write_space();
24184                for (i, ord) in order_by.expressions.iter().enumerate() {
24185                    if i > 0 {
24186                        self.write(", ");
24187                    }
24188                    self.generate_ordered(ord)?;
24189                }
24190            }
24191
24192            if let Some(limit) = &subquery.limit {
24193                self.write_space();
24194                self.write_keyword("LIMIT");
24195                self.write_space();
24196                self.generate_expression(&limit.this)?;
24197                if limit.percent {
24198                    self.write_space();
24199                    self.write_keyword("PERCENT");
24200                }
24201            }
24202
24203            if let Some(offset) = &subquery.offset {
24204                self.write_space();
24205                self.write_keyword("OFFSET");
24206                self.write_space();
24207                self.generate_expression(&offset.this)?;
24208            }
24209        }
24210
24211        if !skip_outer_parens {
24212            if self.config.pretty && is_statement {
24213                self.write_newline();
24214                self.indent_level -= 1;
24215                self.write_indent();
24216            }
24217            self.write(")");
24218        }
24219
24220        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
24221        if !subquery.modifiers_inside {
24222            if let Some(order_by) = &subquery.order_by {
24223                self.write_space();
24224                self.write_keyword("ORDER BY");
24225                self.write_space();
24226                for (i, ord) in order_by.expressions.iter().enumerate() {
24227                    if i > 0 {
24228                        self.write(", ");
24229                    }
24230                    self.generate_ordered(ord)?;
24231                }
24232            }
24233
24234            if let Some(limit) = &subquery.limit {
24235                self.write_space();
24236                self.write_keyword("LIMIT");
24237                self.write_space();
24238                self.generate_expression(&limit.this)?;
24239                if limit.percent {
24240                    self.write_space();
24241                    self.write_keyword("PERCENT");
24242                }
24243            }
24244
24245            if let Some(offset) = &subquery.offset {
24246                self.write_space();
24247                self.write_keyword("OFFSET");
24248                self.write_space();
24249                self.generate_expression(&offset.this)?;
24250            }
24251
24252            // Generate DISTRIBUTE BY (Hive/Spark)
24253            if let Some(distribute_by) = &subquery.distribute_by {
24254                self.write_space();
24255                self.write_keyword("DISTRIBUTE BY");
24256                self.write_space();
24257                for (i, expr) in distribute_by.expressions.iter().enumerate() {
24258                    if i > 0 {
24259                        self.write(", ");
24260                    }
24261                    self.generate_expression(expr)?;
24262                }
24263            }
24264
24265            // Generate SORT BY (Hive/Spark)
24266            if let Some(sort_by) = &subquery.sort_by {
24267                self.write_space();
24268                self.write_keyword("SORT BY");
24269                self.write_space();
24270                for (i, ord) in sort_by.expressions.iter().enumerate() {
24271                    if i > 0 {
24272                        self.write(", ");
24273                    }
24274                    self.generate_ordered(ord)?;
24275                }
24276            }
24277
24278            // Generate CLUSTER BY (Hive/Spark)
24279            if let Some(cluster_by) = &subquery.cluster_by {
24280                self.write_space();
24281                self.write_keyword("CLUSTER BY");
24282                self.write_space();
24283                for (i, ord) in cluster_by.expressions.iter().enumerate() {
24284                    if i > 0 {
24285                        self.write(", ");
24286                    }
24287                    self.generate_ordered(ord)?;
24288                }
24289            }
24290        }
24291
24292        if let Some(alias) = &subquery.alias {
24293            self.write_space();
24294            let skip_as = matches!(self.config.dialect, Some(DialectType::Oracle))
24295                || (matches!(self.config.dialect, Some(DialectType::ClickHouse))
24296                    && !subquery.alias_explicit_as);
24297            if !skip_as {
24298                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24299                    self.write(subquery.alias_keyword.as_deref().unwrap_or("AS"));
24300                } else {
24301                    self.write_keyword("AS");
24302                }
24303                self.write_space();
24304            }
24305            self.generate_identifier(alias)?;
24306            if !subquery.column_aliases.is_empty() {
24307                self.write("(");
24308                for (i, col) in subquery.column_aliases.iter().enumerate() {
24309                    if i > 0 {
24310                        self.write(", ");
24311                    }
24312                    self.generate_identifier(col)?;
24313                }
24314                self.write(")");
24315            }
24316        }
24317        // Output trailing comments
24318        for comment in &subquery.trailing_comments {
24319            self.write(" ");
24320            self.write_formatted_comment(comment);
24321        }
24322        Ok(())
24323    }
24324
24325    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
24326        // Generate WITH clause if present
24327        if let Some(ref with) = pivot.with {
24328            self.generate_with(with)?;
24329            self.write_space();
24330        }
24331
24332        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
24333
24334        // Check for Redshift UNPIVOT in FROM clause:
24335        // UNPIVOT expr [AS val AT attr]
24336        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
24337        let is_redshift_unpivot = pivot.unpivot
24338            && pivot.expressions.is_empty()
24339            && pivot.fields.is_empty()
24340            && pivot.using.is_empty()
24341            && pivot.into.is_none()
24342            && !matches!(&pivot.this, Expression::Null(_));
24343
24344        if is_redshift_unpivot {
24345            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
24346            self.write_keyword("UNPIVOT");
24347            self.write_space();
24348            self.generate_expression(&pivot.this)?;
24349            // Alias - for Redshift it can be "val AT attr" format
24350            if let Some(alias) = &pivot.alias {
24351                self.write_space();
24352                self.write_keyword("AS");
24353                self.write_space();
24354                // The alias might contain " AT " for the attr part
24355                self.write(&alias.name);
24356            }
24357            return Ok(());
24358        }
24359
24360        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
24361        let is_simplified = !pivot.using.is_empty()
24362            || pivot.into.is_some()
24363            || (pivot.fields.is_empty()
24364                && !pivot.expressions.is_empty()
24365                && !matches!(&pivot.this, Expression::Null(_)));
24366
24367        if is_simplified {
24368            // DuckDB simplified syntax:
24369            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
24370            //   UNPIVOT table ON cols INTO NAME col VALUE col
24371            self.write_keyword(direction);
24372            self.write_space();
24373            self.generate_expression(&pivot.this)?;
24374
24375            if !pivot.expressions.is_empty() {
24376                self.write_space();
24377                self.write_keyword("ON");
24378                self.write_space();
24379                for (i, expr) in pivot.expressions.iter().enumerate() {
24380                    if i > 0 {
24381                        self.write(", ");
24382                    }
24383                    self.generate_expression(expr)?;
24384                }
24385            }
24386
24387            // INTO (for UNPIVOT)
24388            if let Some(into) = &pivot.into {
24389                self.write_space();
24390                self.write_keyword("INTO");
24391                self.write_space();
24392                self.generate_expression(into)?;
24393            }
24394
24395            // USING (for PIVOT)
24396            if !pivot.using.is_empty() {
24397                self.write_space();
24398                self.write_keyword("USING");
24399                self.write_space();
24400                for (i, expr) in pivot.using.iter().enumerate() {
24401                    if i > 0 {
24402                        self.write(", ");
24403                    }
24404                    self.generate_expression(expr)?;
24405                }
24406            }
24407
24408            // GROUP BY
24409            if let Some(group) = &pivot.group {
24410                self.write_space();
24411                self.generate_expression(group)?;
24412            }
24413        } else {
24414            // Standard syntax:
24415            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
24416            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
24417            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
24418            if !matches!(&pivot.this, Expression::Null(_)) {
24419                self.generate_expression(&pivot.this)?;
24420                self.write_space();
24421            }
24422            self.write_keyword(direction);
24423            self.write("(");
24424
24425            // Aggregation expressions
24426            for (i, expr) in pivot.expressions.iter().enumerate() {
24427                if i > 0 {
24428                    self.write(", ");
24429                }
24430                self.generate_expression(expr)?;
24431            }
24432
24433            // FOR...IN fields
24434            if !pivot.fields.is_empty() {
24435                if !pivot.expressions.is_empty() {
24436                    self.write_space();
24437                }
24438                self.write_keyword("FOR");
24439                self.write_space();
24440                for (i, field) in pivot.fields.iter().enumerate() {
24441                    if i > 0 {
24442                        self.write_space();
24443                    }
24444                    // field is an In expression: column IN (values)
24445                    self.generate_expression(field)?;
24446                }
24447            }
24448
24449            // DEFAULT ON NULL
24450            if let Some(default_val) = &pivot.default_on_null {
24451                self.write_space();
24452                self.write_keyword("DEFAULT ON NULL");
24453                self.write(" (");
24454                self.generate_expression(default_val)?;
24455                self.write(")");
24456            }
24457
24458            // GROUP BY inside PIVOT parens
24459            if let Some(group) = &pivot.group {
24460                self.write_space();
24461                self.generate_expression(group)?;
24462            }
24463
24464            self.write(")");
24465        }
24466
24467        // Alias
24468        if let Some(alias) = &pivot.alias {
24469            self.write_space();
24470            self.write_keyword("AS");
24471            self.write_space();
24472            self.generate_identifier(alias)?;
24473            self.generate_alias_column_list(&pivot.alias_columns)?;
24474        }
24475
24476        Ok(())
24477    }
24478
24479    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
24480        self.generate_expression(&unpivot.this)?;
24481        self.write_space();
24482        self.write_keyword("UNPIVOT");
24483        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
24484        if let Some(include) = unpivot.include_nulls {
24485            self.write_space();
24486            if include {
24487                self.write_keyword("INCLUDE NULLS");
24488            } else {
24489                self.write_keyword("EXCLUDE NULLS");
24490            }
24491            self.write_space();
24492        }
24493        self.write("(");
24494        if unpivot.value_column_parenthesized {
24495            self.write("(");
24496        }
24497        self.generate_identifier(&unpivot.value_column)?;
24498        // Output additional value columns if present
24499        for extra_col in &unpivot.extra_value_columns {
24500            self.write(", ");
24501            self.generate_identifier(extra_col)?;
24502        }
24503        if unpivot.value_column_parenthesized {
24504            self.write(")");
24505        }
24506        self.write_space();
24507        self.write_keyword("FOR");
24508        self.write_space();
24509        self.generate_identifier(&unpivot.name_column)?;
24510        self.write_space();
24511        self.write_keyword("IN");
24512        self.write(" (");
24513        for (i, col) in unpivot.columns.iter().enumerate() {
24514            if i > 0 {
24515                self.write(", ");
24516            }
24517            self.generate_expression(col)?;
24518        }
24519        self.write("))");
24520        if let Some(alias) = &unpivot.alias {
24521            self.write_space();
24522            self.write_keyword("AS");
24523            self.write_space();
24524            self.generate_identifier(alias)?;
24525            self.generate_alias_column_list(&unpivot.alias_columns)?;
24526        }
24527        Ok(())
24528    }
24529
24530    fn generate_alias_column_list(&mut self, columns: &[Identifier]) -> Result<()> {
24531        if columns.is_empty() {
24532            return Ok(());
24533        }
24534
24535        self.write("(");
24536        for (i, column) in columns.iter().enumerate() {
24537            if i > 0 {
24538                self.write(", ");
24539            }
24540            self.generate_identifier(column)?;
24541        }
24542        self.write(")");
24543        Ok(())
24544    }
24545
24546    fn generate_values(&mut self, values: &Values) -> Result<()> {
24547        self.write_keyword("VALUES");
24548        for (i, row) in values.expressions.iter().enumerate() {
24549            if i > 0 {
24550                self.write(",");
24551            }
24552            self.write(" (");
24553            for (j, expr) in row.expressions.iter().enumerate() {
24554                if j > 0 {
24555                    self.write(", ");
24556                }
24557                self.generate_expression(expr)?;
24558            }
24559            self.write(")");
24560        }
24561        if let Some(alias) = &values.alias {
24562            self.write_space();
24563            self.write_keyword("AS");
24564            self.write_space();
24565            self.generate_identifier(alias)?;
24566            if !values.column_aliases.is_empty() {
24567                self.write("(");
24568                for (i, col) in values.column_aliases.iter().enumerate() {
24569                    if i > 0 {
24570                        self.write(", ");
24571                    }
24572                    self.generate_identifier(col)?;
24573                }
24574                self.write(")");
24575            }
24576        }
24577        Ok(())
24578    }
24579
24580    fn generate_array(&mut self, arr: &Array) -> Result<()> {
24581        // Apply struct name inheritance for target dialects that need it
24582        let needs_inheritance = matches!(
24583            self.config.dialect,
24584            Some(DialectType::DuckDB)
24585                | Some(DialectType::Spark)
24586                | Some(DialectType::Databricks)
24587                | Some(DialectType::Hive)
24588                | Some(DialectType::Snowflake)
24589                | Some(DialectType::Presto)
24590                | Some(DialectType::Trino)
24591        );
24592        let propagated: Vec<Expression>;
24593        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
24594            propagated = Self::inherit_struct_field_names(&arr.expressions);
24595            &propagated
24596        } else {
24597            &arr.expressions
24598        };
24599
24600        // Generic mode: ARRAY(1, 2, 3) with parentheses
24601        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
24602        let use_parens =
24603            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
24604        if !self.config.array_bracket_only {
24605            self.write_keyword("ARRAY");
24606        }
24607        if use_parens {
24608            self.write("(");
24609        } else {
24610            self.write("[");
24611        }
24612        for (i, expr) in expressions.iter().enumerate() {
24613            if i > 0 {
24614                self.write(", ");
24615            }
24616            self.generate_expression(expr)?;
24617        }
24618        if use_parens {
24619            self.write(")");
24620        } else {
24621            self.write("]");
24622        }
24623        Ok(())
24624    }
24625
24626    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
24627        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
24628        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
24629        if tuple.expressions.len() == 2 {
24630            if let Expression::TableAlias(_) = &tuple.expressions[1] {
24631                // First element is the function/expression, second is the TableAlias
24632                self.generate_expression(&tuple.expressions[0])?;
24633                self.write_space();
24634                self.write_keyword("AS");
24635                self.write_space();
24636                self.generate_expression(&tuple.expressions[1])?;
24637                return Ok(());
24638            }
24639        }
24640
24641        // In pretty mode, format long tuples with each element on a new line
24642        // Only expand if total width exceeds threshold
24643        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
24644            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
24645            for expr in &tuple.expressions {
24646                expr_strings.push(self.generate_to_string(expr)?);
24647            }
24648            self.too_wide(&expr_strings)
24649        } else {
24650            false
24651        };
24652
24653        if expand_tuple {
24654            self.write("(");
24655            self.write_newline();
24656            self.indent_level += 1;
24657            for (i, expr) in tuple.expressions.iter().enumerate() {
24658                if i > 0 {
24659                    self.write(",");
24660                    self.write_newline();
24661                }
24662                self.write_indent();
24663                self.generate_expression(expr)?;
24664            }
24665            self.indent_level -= 1;
24666            self.write_newline();
24667            self.write_indent();
24668            self.write(")");
24669        } else {
24670            self.write("(");
24671            for (i, expr) in tuple.expressions.iter().enumerate() {
24672                if i > 0 {
24673                    self.write(", ");
24674                }
24675                self.generate_expression(expr)?;
24676            }
24677            self.write(")");
24678        }
24679        Ok(())
24680    }
24681
24682    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
24683        self.generate_expression(&pipe.this)?;
24684        self.write(" |> ");
24685        self.generate_expression(&pipe.expression)?;
24686        Ok(())
24687    }
24688
24689    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
24690        let unsupported_tsql_null_ordering = ordered.nulls_first.is_some()
24691            && !self.config.null_ordering_supported
24692            && matches!(
24693                self.config.dialect,
24694                Some(DialectType::TSQL) | Some(DialectType::Fabric)
24695            );
24696        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
24697        let emulate_tsql_null_ordering = if let Some(nulls_first) = ordered.nulls_first {
24698            let target_default_nulls_first = !ordered.desc;
24699
24700            unsupported_tsql_null_ordering
24701                && nulls_first != target_default_nulls_first
24702                && !random_ordering
24703        } else {
24704            false
24705        };
24706
24707        if emulate_tsql_null_ordering {
24708            if Self::is_integer_ordering_literal(&ordered.this) {
24709                let nulls_order = if ordered.nulls_first == Some(true) {
24710                    "NULLS FIRST"
24711                } else {
24712                    "NULLS LAST"
24713                };
24714                self.unsupported(format!(
24715                    "'{nulls_order}' translation not supported with positional ordering"
24716                ))?;
24717            } else {
24718                self.write_keyword("CASE WHEN");
24719                self.write_space();
24720                self.generate_expression(&ordered.this)?;
24721                self.write_space();
24722                self.write_keyword("IS NULL THEN 1 ELSE 0 END");
24723                if ordered.nulls_first == Some(true) {
24724                    self.write_space();
24725                    self.write_keyword("DESC");
24726                }
24727                self.write(", ");
24728            }
24729        }
24730
24731        self.generate_expression(&ordered.this)?;
24732        if ordered.desc {
24733            self.write_space();
24734            self.write_keyword("DESC");
24735        } else if ordered.explicit_asc {
24736            self.write_space();
24737            self.write_keyword("ASC");
24738        }
24739        if let Some(nulls_first) = ordered.nulls_first {
24740            if !unsupported_tsql_null_ordering
24741                && (self.config.null_ordering_supported
24742                    || !matches!(self.config.dialect, Some(DialectType::Fabric)))
24743            {
24744                // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
24745                // for the dialect. Different dialects have different NULL ordering defaults:
24746                //
24747                // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
24748                //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
24749                //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
24750                //
24751                // nulls_are_small (Spark, Hive, BigQuery, most others):
24752                //   - ASC: NULLS FIRST is default
24753                //   - DESC: NULLS LAST is default
24754                //
24755                // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
24756                //   - NULLS LAST is always the default regardless of sort direction
24757                let is_asc = !ordered.desc;
24758                let is_nulls_are_large = matches!(
24759                    self.config.dialect,
24760                    Some(DialectType::Oracle)
24761                        | Some(DialectType::PostgreSQL)
24762                        | Some(DialectType::Redshift)
24763                        | Some(DialectType::Snowflake)
24764                );
24765                let is_nulls_are_last = matches!(
24766                    self.config.dialect,
24767                    Some(DialectType::Dremio)
24768                        | Some(DialectType::DuckDB)
24769                        | Some(DialectType::Presto)
24770                        | Some(DialectType::Trino)
24771                        | Some(DialectType::Athena)
24772                        | Some(DialectType::ClickHouse)
24773                        | Some(DialectType::Drill)
24774                        | Some(DialectType::Exasol)
24775                );
24776
24777                // Check if the NULLS ordering matches the default for this dialect
24778                let is_default_nulls = if is_nulls_are_large {
24779                    // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
24780                    (is_asc && !nulls_first) || (!is_asc && nulls_first)
24781                } else if is_nulls_are_last {
24782                    // For nulls_are_last: NULLS LAST is always default
24783                    !nulls_first
24784                } else {
24785                    false
24786                };
24787
24788                if !is_default_nulls {
24789                    self.write_space();
24790                    self.write_keyword("NULLS");
24791                    self.write_space();
24792                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
24793                }
24794            }
24795        }
24796        // WITH FILL clause (ClickHouse)
24797        if let Some(ref with_fill) = ordered.with_fill {
24798            self.write_space();
24799            self.generate_with_fill(with_fill)?;
24800        }
24801        Ok(())
24802    }
24803
24804    fn is_integer_ordering_literal(expr: &Expression) -> bool {
24805        matches!(
24806            expr,
24807            Expression::Literal(lit)
24808                if matches!(lit.as_ref(), Literal::Number(n) if n.parse::<u64>().is_ok())
24809        )
24810    }
24811
24812    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
24813    fn write_clickhouse_type(&mut self, type_str: &str) {
24814        if self.clickhouse_nullable_depth < 0 {
24815            // Map key context: don't wrap in Nullable
24816            self.write(type_str);
24817        } else {
24818            self.write(&format!("Nullable({})", type_str));
24819        }
24820    }
24821
24822    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
24823        use crate::dialects::DialectType;
24824
24825        match dt {
24826            DataType::Boolean => {
24827                // Dialect-specific boolean type mappings
24828                match self.config.dialect {
24829                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
24830                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
24831                    Some(DialectType::Oracle) => {
24832                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
24833                        self.write_keyword("NUMBER(1)")
24834                    }
24835                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
24836                    _ => self.write_keyword("BOOLEAN"),
24837                }
24838            }
24839            DataType::TinyInt { length } => {
24840                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
24841                // Dremio maps TINYINT to INT
24842                // ClickHouse maps TINYINT to Int8
24843                match self.config.dialect {
24844                    Some(DialectType::PostgreSQL)
24845                    | Some(DialectType::Redshift)
24846                    | Some(DialectType::Oracle)
24847                    | Some(DialectType::Exasol) => {
24848                        self.write_keyword("SMALLINT");
24849                    }
24850                    Some(DialectType::Teradata) => {
24851                        // Teradata uses BYTEINT for smallest integer
24852                        self.write_keyword("BYTEINT");
24853                    }
24854                    Some(DialectType::Dremio) => {
24855                        // Dremio maps TINYINT to INT
24856                        self.write_keyword("INT");
24857                    }
24858                    Some(DialectType::ClickHouse) => {
24859                        self.write_clickhouse_type("Int8");
24860                    }
24861                    _ => {
24862                        self.write_keyword("TINYINT");
24863                    }
24864                }
24865                if let Some(n) = length {
24866                    if !matches!(
24867                        self.config.dialect,
24868                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
24869                    ) {
24870                        self.write(&format!("({})", n));
24871                    }
24872                }
24873            }
24874            DataType::SmallInt { length } => {
24875                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
24876                match self.config.dialect {
24877                    Some(DialectType::Dremio) => {
24878                        self.write_keyword("INT");
24879                    }
24880                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
24881                        self.write_keyword("INTEGER");
24882                    }
24883                    Some(DialectType::BigQuery) => {
24884                        self.write_keyword("INT64");
24885                    }
24886                    Some(DialectType::ClickHouse) => {
24887                        self.write_clickhouse_type("Int16");
24888                    }
24889                    _ => {
24890                        self.write_keyword("SMALLINT");
24891                        if let Some(n) = length {
24892                            self.write(&format!("({})", n));
24893                        }
24894                    }
24895                }
24896            }
24897            DataType::Int {
24898                length,
24899                integer_spelling: _,
24900            } => {
24901                // BigQuery uses INT64 for INT
24902                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
24903                    self.write_keyword("INT64");
24904                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24905                    self.write_clickhouse_type("Int32");
24906                } else {
24907                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
24908                    let use_integer = match self.config.dialect {
24909                        Some(DialectType::TSQL)
24910                        | Some(DialectType::Fabric)
24911                        | Some(DialectType::Presto)
24912                        | Some(DialectType::Trino)
24913                        | Some(DialectType::SQLite)
24914                        | Some(DialectType::Redshift) => true,
24915                        _ => false,
24916                    };
24917                    if use_integer {
24918                        self.write_keyword("INTEGER");
24919                    } else {
24920                        self.write_keyword("INT");
24921                    }
24922                    if let Some(n) = length {
24923                        self.write(&format!("({})", n));
24924                    }
24925                }
24926            }
24927            DataType::BigInt { length } => {
24928                // Dialect-specific bigint type mappings
24929                match self.config.dialect {
24930                    Some(DialectType::Oracle) => {
24931                        // Oracle doesn't have BIGINT, uses INT
24932                        self.write_keyword("INT");
24933                    }
24934                    Some(DialectType::ClickHouse) => {
24935                        self.write_clickhouse_type("Int64");
24936                    }
24937                    _ => {
24938                        self.write_keyword("BIGINT");
24939                        if let Some(n) = length {
24940                            self.write(&format!("({})", n));
24941                        }
24942                    }
24943                }
24944            }
24945            DataType::Float {
24946                precision,
24947                scale,
24948                real_spelling,
24949            } => {
24950                // Dialect-specific float type mappings
24951                // If real_spelling is true, preserve REAL; otherwise use dialect default
24952                // Spark/Hive don't support REAL, always use FLOAT
24953                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24954                    self.write_clickhouse_type("Float32");
24955                } else if *real_spelling
24956                    && !matches!(
24957                        self.config.dialect,
24958                        Some(DialectType::Spark)
24959                            | Some(DialectType::Databricks)
24960                            | Some(DialectType::Hive)
24961                            | Some(DialectType::Snowflake)
24962                            | Some(DialectType::MySQL)
24963                            | Some(DialectType::BigQuery)
24964                    )
24965                {
24966                    self.write_keyword("REAL")
24967                } else {
24968                    match self.config.dialect {
24969                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
24970                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
24971                        _ => self.write_keyword("FLOAT"),
24972                    }
24973                }
24974                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
24975                // Spark/Hive don't support FLOAT(precision)
24976                if !matches!(
24977                    self.config.dialect,
24978                    Some(DialectType::Spark)
24979                        | Some(DialectType::Databricks)
24980                        | Some(DialectType::Hive)
24981                        | Some(DialectType::Presto)
24982                        | Some(DialectType::Trino)
24983                ) {
24984                    if let Some(p) = precision {
24985                        self.write(&format!("({}", p));
24986                        if let Some(s) = scale {
24987                            self.write(&format!(", {})", s));
24988                        } else {
24989                            self.write(")");
24990                        }
24991                    }
24992                }
24993            }
24994            DataType::Double { precision, scale } => {
24995                // Dialect-specific double type mappings
24996                match self.config.dialect {
24997                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24998                        self.write_keyword("FLOAT")
24999                    } // SQL Server/Fabric FLOAT is double
25000                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
25001                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
25002                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
25003                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
25004                    Some(DialectType::PostgreSQL)
25005                    | Some(DialectType::Redshift)
25006                    | Some(DialectType::Teradata)
25007                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
25008                    _ => self.write_keyword("DOUBLE"),
25009                }
25010                // MySQL supports DOUBLE(precision, scale)
25011                if let Some(p) = precision {
25012                    self.write(&format!("({}", p));
25013                    if let Some(s) = scale {
25014                        self.write(&format!(", {})", s));
25015                    } else {
25016                        self.write(")");
25017                    }
25018                }
25019            }
25020            DataType::Decimal { precision, scale } => {
25021                // Dialect-specific decimal type mappings
25022                match self.config.dialect {
25023                    Some(DialectType::ClickHouse) => {
25024                        self.write("Decimal");
25025                        if let Some(p) = precision {
25026                            self.write(&format!("({}", p));
25027                            if let Some(s) = scale {
25028                                self.write(&format!(", {}", s));
25029                            }
25030                            self.write(")");
25031                        }
25032                    }
25033                    Some(DialectType::Oracle) => {
25034                        // Oracle uses NUMBER instead of DECIMAL
25035                        self.write_keyword("NUMBER");
25036                        if let Some(p) = precision {
25037                            self.write(&format!("({}", p));
25038                            if let Some(s) = scale {
25039                                self.write(&format!(", {}", s));
25040                            }
25041                            self.write(")");
25042                        }
25043                    }
25044                    Some(DialectType::BigQuery) => {
25045                        // BigQuery uses NUMERIC instead of DECIMAL
25046                        self.write_keyword("NUMERIC");
25047                        if let Some(p) = precision {
25048                            self.write(&format!("({}", p));
25049                            if let Some(s) = scale {
25050                                self.write(&format!(", {}", s));
25051                            }
25052                            self.write(")");
25053                        }
25054                    }
25055                    _ => {
25056                        self.write_keyword("DECIMAL");
25057                        if let Some(p) = precision {
25058                            self.write(&format!("({}", p));
25059                            if let Some(s) = scale {
25060                                self.write(&format!(", {}", s));
25061                            }
25062                            self.write(")");
25063                        }
25064                    }
25065                }
25066            }
25067            DataType::Char { length } => {
25068                // Dialect-specific char type mappings
25069                match self.config.dialect {
25070                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
25071                        // DuckDB/SQLite maps CHAR to TEXT
25072                        self.write_keyword("TEXT");
25073                    }
25074                    Some(DialectType::Hive)
25075                    | Some(DialectType::Spark)
25076                    | Some(DialectType::Databricks) => {
25077                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
25078                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
25079                        if length.is_some()
25080                            && !matches!(self.config.dialect, Some(DialectType::Hive))
25081                        {
25082                            self.write_keyword("CHAR");
25083                            if let Some(n) = length {
25084                                self.write(&format!("({})", n));
25085                            }
25086                        } else {
25087                            self.write_keyword("STRING");
25088                        }
25089                    }
25090                    Some(DialectType::Dremio) => {
25091                        // Dremio maps CHAR to VARCHAR
25092                        self.write_keyword("VARCHAR");
25093                        if let Some(n) = length {
25094                            self.write(&format!("({})", n));
25095                        }
25096                    }
25097                    _ => {
25098                        self.write_keyword("CHAR");
25099                        if let Some(n) = length {
25100                            self.write(&format!("({})", n));
25101                        }
25102                    }
25103                }
25104            }
25105            DataType::VarChar {
25106                length,
25107                parenthesized_length,
25108            } => {
25109                // Dialect-specific varchar type mappings
25110                match self.config.dialect {
25111                    Some(DialectType::Oracle) => {
25112                        self.write_keyword("VARCHAR2");
25113                        if let Some(n) = length {
25114                            self.write(&format!("({})", n));
25115                        }
25116                    }
25117                    Some(DialectType::DuckDB) => {
25118                        // DuckDB maps VARCHAR to TEXT, preserving length
25119                        self.write_keyword("TEXT");
25120                        if let Some(n) = length {
25121                            self.write(&format!("({})", n));
25122                        }
25123                    }
25124                    Some(DialectType::SQLite) => {
25125                        // SQLite maps VARCHAR to TEXT, preserving length
25126                        self.write_keyword("TEXT");
25127                        if let Some(n) = length {
25128                            self.write(&format!("({})", n));
25129                        }
25130                    }
25131                    Some(DialectType::MySQL) if length.is_none() => {
25132                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
25133                        self.write_keyword("TEXT");
25134                    }
25135                    Some(DialectType::Hive)
25136                    | Some(DialectType::Spark)
25137                    | Some(DialectType::Databricks)
25138                        if length.is_none() =>
25139                    {
25140                        // Hive/Spark/Databricks: VARCHAR without length → STRING
25141                        self.write_keyword("STRING");
25142                    }
25143                    _ => {
25144                        self.write_keyword("VARCHAR");
25145                        if let Some(n) = length {
25146                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
25147                            if *parenthesized_length {
25148                                self.write(&format!("(({}))", n));
25149                            } else {
25150                                self.write(&format!("({})", n));
25151                            }
25152                        }
25153                    }
25154                }
25155            }
25156            DataType::Text => {
25157                // Dialect-specific text type mappings
25158                match self.config.dialect {
25159                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
25160                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25161                        self.write_keyword("VARCHAR(MAX)")
25162                    }
25163                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
25164                    Some(DialectType::Snowflake)
25165                    | Some(DialectType::Dremio)
25166                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
25167                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
25168                    Some(DialectType::Presto)
25169                    | Some(DialectType::Trino)
25170                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
25171                    Some(DialectType::Spark)
25172                    | Some(DialectType::Databricks)
25173                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
25174                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
25175                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
25176                        self.write_keyword("STRING")
25177                    }
25178                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
25179                    _ => self.write_keyword("TEXT"),
25180                }
25181            }
25182            DataType::TextWithLength { length } => {
25183                // TEXT(n) - dialect-specific type with length
25184                match self.config.dialect {
25185                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
25186                    Some(DialectType::Hive)
25187                    | Some(DialectType::Spark)
25188                    | Some(DialectType::Databricks) => {
25189                        self.write(&format!("VARCHAR({})", length));
25190                    }
25191                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
25192                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
25193                    Some(DialectType::Snowflake)
25194                    | Some(DialectType::Presto)
25195                    | Some(DialectType::Trino)
25196                    | Some(DialectType::Athena)
25197                    | Some(DialectType::Drill)
25198                    | Some(DialectType::Dremio) => {
25199                        self.write(&format!("VARCHAR({})", length));
25200                    }
25201                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25202                        self.write(&format!("VARCHAR({})", length))
25203                    }
25204                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
25205                        self.write(&format!("STRING({})", length))
25206                    }
25207                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
25208                    _ => self.write(&format!("TEXT({})", length)),
25209                }
25210            }
25211            DataType::String { length } => {
25212                // STRING type with optional length (BigQuery STRING(n))
25213                match self.config.dialect {
25214                    Some(DialectType::ClickHouse) => {
25215                        // ClickHouse uses String with specific casing
25216                        self.write("String");
25217                        if let Some(n) = length {
25218                            self.write(&format!("({})", n));
25219                        }
25220                    }
25221                    Some(DialectType::BigQuery)
25222                    | Some(DialectType::Hive)
25223                    | Some(DialectType::Spark)
25224                    | Some(DialectType::Databricks)
25225                    | Some(DialectType::StarRocks)
25226                    | Some(DialectType::Doris) => {
25227                        self.write_keyword("STRING");
25228                        if let Some(n) = length {
25229                            self.write(&format!("({})", n));
25230                        }
25231                    }
25232                    Some(DialectType::PostgreSQL) => {
25233                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
25234                        if let Some(n) = length {
25235                            self.write_keyword("VARCHAR");
25236                            self.write(&format!("({})", n));
25237                        } else {
25238                            self.write_keyword("TEXT");
25239                        }
25240                    }
25241                    Some(DialectType::Redshift) => {
25242                        // Redshift: STRING -> VARCHAR(MAX)
25243                        if let Some(n) = length {
25244                            self.write_keyword("VARCHAR");
25245                            self.write(&format!("({})", n));
25246                        } else {
25247                            self.write_keyword("VARCHAR(MAX)");
25248                        }
25249                    }
25250                    Some(DialectType::MySQL) => {
25251                        // MySQL doesn't have STRING - use VARCHAR or TEXT
25252                        if let Some(n) = length {
25253                            self.write_keyword("VARCHAR");
25254                            self.write(&format!("({})", n));
25255                        } else {
25256                            self.write_keyword("TEXT");
25257                        }
25258                    }
25259                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25260                        // TSQL: STRING -> VARCHAR(MAX)
25261                        if let Some(n) = length {
25262                            self.write_keyword("VARCHAR");
25263                            self.write(&format!("({})", n));
25264                        } else {
25265                            self.write_keyword("VARCHAR(MAX)");
25266                        }
25267                    }
25268                    Some(DialectType::Oracle) => {
25269                        // Oracle: STRING -> CLOB
25270                        self.write_keyword("CLOB");
25271                    }
25272                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
25273                        // DuckDB/Materialize uses TEXT for string types
25274                        self.write_keyword("TEXT");
25275                        if let Some(n) = length {
25276                            self.write(&format!("({})", n));
25277                        }
25278                    }
25279                    Some(DialectType::Presto)
25280                    | Some(DialectType::Trino)
25281                    | Some(DialectType::Drill)
25282                    | Some(DialectType::Dremio) => {
25283                        // Presto/Trino/Drill use VARCHAR for string types
25284                        self.write_keyword("VARCHAR");
25285                        if let Some(n) = length {
25286                            self.write(&format!("({})", n));
25287                        }
25288                    }
25289                    Some(DialectType::Snowflake) => {
25290                        // Snowflake: STRING stays as STRING (identity/DDL)
25291                        // CAST context STRING -> VARCHAR is handled in generate_cast
25292                        self.write_keyword("STRING");
25293                        if let Some(n) = length {
25294                            self.write(&format!("({})", n));
25295                        }
25296                    }
25297                    _ => {
25298                        // Default: output STRING with optional length
25299                        self.write_keyword("STRING");
25300                        if let Some(n) = length {
25301                            self.write(&format!("({})", n));
25302                        }
25303                    }
25304                }
25305            }
25306            DataType::Binary { length } => {
25307                // Dialect-specific binary type mappings
25308                match self.config.dialect {
25309                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25310                        self.write_keyword("BYTEA");
25311                        if let Some(n) = length {
25312                            self.write(&format!("({})", n));
25313                        }
25314                    }
25315                    Some(DialectType::Redshift) => {
25316                        self.write_keyword("VARBYTE");
25317                        if let Some(n) = length {
25318                            self.write(&format!("({})", n));
25319                        }
25320                    }
25321                    Some(DialectType::DuckDB)
25322                    | Some(DialectType::SQLite)
25323                    | Some(DialectType::Oracle) => {
25324                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
25325                        self.write_keyword("BLOB");
25326                        if let Some(n) = length {
25327                            self.write(&format!("({})", n));
25328                        }
25329                    }
25330                    Some(DialectType::Presto)
25331                    | Some(DialectType::Trino)
25332                    | Some(DialectType::Athena)
25333                    | Some(DialectType::Drill)
25334                    | Some(DialectType::Dremio) => {
25335                        // These dialects map BINARY to VARBINARY
25336                        self.write_keyword("VARBINARY");
25337                        if let Some(n) = length {
25338                            self.write(&format!("({})", n));
25339                        }
25340                    }
25341                    Some(DialectType::ClickHouse) => {
25342                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
25343                        if self.clickhouse_nullable_depth < 0 {
25344                            self.write("BINARY");
25345                        } else {
25346                            self.write("Nullable(BINARY");
25347                        }
25348                        if let Some(n) = length {
25349                            self.write(&format!("({})", n));
25350                        }
25351                        if self.clickhouse_nullable_depth >= 0 {
25352                            self.write(")");
25353                        }
25354                    }
25355                    _ => {
25356                        self.write_keyword("BINARY");
25357                        if let Some(n) = length {
25358                            self.write(&format!("({})", n));
25359                        }
25360                    }
25361                }
25362            }
25363            DataType::VarBinary { length } => {
25364                // Dialect-specific varbinary type mappings
25365                match self.config.dialect {
25366                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
25367                        self.write_keyword("BYTEA");
25368                        if let Some(n) = length {
25369                            self.write(&format!("({})", n));
25370                        }
25371                    }
25372                    Some(DialectType::Redshift) => {
25373                        self.write_keyword("VARBYTE");
25374                        if let Some(n) = length {
25375                            self.write(&format!("({})", n));
25376                        }
25377                    }
25378                    Some(DialectType::DuckDB)
25379                    | Some(DialectType::SQLite)
25380                    | Some(DialectType::Oracle) => {
25381                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
25382                        self.write_keyword("BLOB");
25383                        if let Some(n) = length {
25384                            self.write(&format!("({})", n));
25385                        }
25386                    }
25387                    Some(DialectType::Exasol) => {
25388                        // Exasol maps VARBINARY to VARCHAR
25389                        self.write_keyword("VARCHAR");
25390                    }
25391                    Some(DialectType::Spark)
25392                    | Some(DialectType::Hive)
25393                    | Some(DialectType::Databricks) => {
25394                        // Spark/Hive use BINARY instead of VARBINARY
25395                        self.write_keyword("BINARY");
25396                        if let Some(n) = length {
25397                            self.write(&format!("({})", n));
25398                        }
25399                    }
25400                    Some(DialectType::ClickHouse) => {
25401                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
25402                        self.write_clickhouse_type("String");
25403                    }
25404                    _ => {
25405                        self.write_keyword("VARBINARY");
25406                        if let Some(n) = length {
25407                            self.write(&format!("({})", n));
25408                        }
25409                    }
25410                }
25411            }
25412            DataType::Blob => {
25413                // Dialect-specific blob type mappings
25414                match self.config.dialect {
25415                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
25416                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
25417                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
25418                        self.write_keyword("VARBINARY")
25419                    }
25420                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
25421                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
25422                    Some(DialectType::Presto)
25423                    | Some(DialectType::Trino)
25424                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
25425                    Some(DialectType::DuckDB) => {
25426                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
25427                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
25428                        self.write_keyword("VARBINARY");
25429                    }
25430                    Some(DialectType::Spark)
25431                    | Some(DialectType::Databricks)
25432                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
25433                    Some(DialectType::ClickHouse) => {
25434                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
25435                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
25436                        // This matches Python sqlglot behavior.
25437                        self.write("Nullable(String)");
25438                    }
25439                    _ => self.write_keyword("BLOB"),
25440                }
25441            }
25442            DataType::Bit { length } => {
25443                // Dialect-specific bit type mappings
25444                match self.config.dialect {
25445                    Some(DialectType::Dremio)
25446                    | Some(DialectType::Spark)
25447                    | Some(DialectType::Databricks)
25448                    | Some(DialectType::Hive)
25449                    | Some(DialectType::Snowflake)
25450                    | Some(DialectType::BigQuery)
25451                    | Some(DialectType::Presto)
25452                    | Some(DialectType::Trino)
25453                    | Some(DialectType::ClickHouse)
25454                    | Some(DialectType::Redshift) => {
25455                        // These dialects don't support BIT type, use BOOLEAN
25456                        self.write_keyword("BOOLEAN");
25457                    }
25458                    _ => {
25459                        self.write_keyword("BIT");
25460                        if let Some(n) = length {
25461                            self.write(&format!("({})", n));
25462                        }
25463                    }
25464                }
25465            }
25466            DataType::VarBit { length } => {
25467                self.write_keyword("VARBIT");
25468                if let Some(n) = length {
25469                    self.write(&format!("({})", n));
25470                }
25471            }
25472            DataType::Date => self.write_keyword("DATE"),
25473            DataType::Time {
25474                precision,
25475                timezone,
25476            } => {
25477                if *timezone {
25478                    // Dialect-specific TIME WITH TIME ZONE output
25479                    match self.config.dialect {
25480                        Some(DialectType::DuckDB) => {
25481                            // DuckDB: TIMETZ (drops precision)
25482                            self.write_keyword("TIMETZ");
25483                        }
25484                        Some(DialectType::PostgreSQL) => {
25485                            // PostgreSQL: TIMETZ or TIMETZ(p)
25486                            self.write_keyword("TIMETZ");
25487                            if let Some(p) = precision {
25488                                self.write(&format!("({})", p));
25489                            }
25490                        }
25491                        _ => {
25492                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
25493                            self.write_keyword("TIME");
25494                            if let Some(p) = precision {
25495                                self.write(&format!("({})", p));
25496                            }
25497                            self.write_keyword(" WITH TIME ZONE");
25498                        }
25499                    }
25500                } else {
25501                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
25502                    if matches!(
25503                        self.config.dialect,
25504                        Some(DialectType::Spark)
25505                            | Some(DialectType::Databricks)
25506                            | Some(DialectType::Hive)
25507                    ) {
25508                        self.write_keyword("TIMESTAMP");
25509                    } else {
25510                        self.write_keyword("TIME");
25511                        if let Some(p) = precision {
25512                            self.write(&format!("({})", p));
25513                        }
25514                    }
25515                }
25516            }
25517            DataType::Timestamp {
25518                precision,
25519                timezone,
25520            } => {
25521                // Dialect-specific timestamp type mappings
25522                match self.config.dialect {
25523                    Some(DialectType::Snowflake) if *timezone => {
25524                        self.write_keyword("TIMESTAMPTZ");
25525                        if let Some(p) = precision {
25526                            self.write(&format!("({})", p));
25527                        }
25528                    }
25529                    Some(DialectType::ClickHouse) => {
25530                        self.write("DateTime");
25531                        if let Some(p) = precision {
25532                            self.write(&format!("({})", p));
25533                        }
25534                    }
25535                    Some(DialectType::TSQL) => {
25536                        if *timezone {
25537                            self.write_keyword("DATETIMEOFFSET");
25538                        } else {
25539                            self.write_keyword("DATETIME2");
25540                        }
25541                        if let Some(p) = precision {
25542                            self.write(&format!("({})", p));
25543                        }
25544                    }
25545                    Some(DialectType::MySQL) => {
25546                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
25547                        self.write_keyword("TIMESTAMP");
25548                        if let Some(p) = precision {
25549                            self.write(&format!("({})", p));
25550                        }
25551                    }
25552                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
25553                        // Doris/StarRocks: TIMESTAMP -> DATETIME
25554                        self.write_keyword("DATETIME");
25555                        if let Some(p) = precision {
25556                            self.write(&format!("({})", p));
25557                        }
25558                    }
25559                    Some(DialectType::BigQuery) => {
25560                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
25561                        if *timezone {
25562                            self.write_keyword("TIMESTAMP");
25563                        } else {
25564                            self.write_keyword("DATETIME");
25565                        }
25566                    }
25567                    Some(DialectType::DuckDB) => {
25568                        // DuckDB: TIMESTAMPTZ shorthand
25569                        if *timezone {
25570                            self.write_keyword("TIMESTAMPTZ");
25571                        } else {
25572                            self.write_keyword("TIMESTAMP");
25573                            if let Some(p) = precision {
25574                                self.write(&format!("({})", p));
25575                            }
25576                        }
25577                    }
25578                    _ => {
25579                        if *timezone && !self.config.tz_to_with_time_zone {
25580                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
25581                            self.write_keyword("TIMESTAMPTZ");
25582                            if let Some(p) = precision {
25583                                self.write(&format!("({})", p));
25584                            }
25585                        } else {
25586                            self.write_keyword("TIMESTAMP");
25587                            if let Some(p) = precision {
25588                                self.write(&format!("({})", p));
25589                            }
25590                            if *timezone {
25591                                self.write_space();
25592                                self.write_keyword("WITH TIME ZONE");
25593                            }
25594                        }
25595                    }
25596                }
25597            }
25598            DataType::Interval { unit, to } => {
25599                self.write_keyword("INTERVAL");
25600                if let Some(u) = unit {
25601                    self.write_space();
25602                    self.write_keyword(u);
25603                }
25604                // Handle range intervals like DAY TO HOUR
25605                if let Some(t) = to {
25606                    self.write_space();
25607                    self.write_keyword("TO");
25608                    self.write_space();
25609                    self.write_keyword(t);
25610                }
25611            }
25612            DataType::Json => {
25613                // Dialect-specific JSON type mappings
25614                match self.config.dialect {
25615                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
25616                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
25617                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
25618                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
25619                    _ => self.write_keyword("JSON"),
25620                }
25621            }
25622            DataType::JsonB => {
25623                // JSONB is PostgreSQL specific, but Doris also supports it
25624                match self.config.dialect {
25625                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
25626                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
25627                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
25628                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
25629                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
25630                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
25631                }
25632            }
25633            DataType::Uuid => {
25634                // Dialect-specific UUID type mappings
25635                match self.config.dialect {
25636                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
25637                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
25638                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
25639                    Some(DialectType::BigQuery)
25640                    | Some(DialectType::Spark)
25641                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
25642                    _ => self.write_keyword("UUID"),
25643                }
25644            }
25645            DataType::Array {
25646                element_type,
25647                dimension,
25648            } => {
25649                // Dialect-specific array syntax
25650                match self.config.dialect {
25651                    Some(DialectType::PostgreSQL)
25652                    | Some(DialectType::Redshift)
25653                    | Some(DialectType::DuckDB) => {
25654                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
25655                        self.generate_data_type(element_type)?;
25656                        if let Some(dim) = dimension {
25657                            self.write(&format!("[{}]", dim));
25658                        } else {
25659                            self.write("[]");
25660                        }
25661                    }
25662                    Some(DialectType::BigQuery) => {
25663                        self.write_keyword("ARRAY<");
25664                        self.generate_data_type(element_type)?;
25665                        self.write(">");
25666                    }
25667                    Some(DialectType::Snowflake)
25668                    | Some(DialectType::Presto)
25669                    | Some(DialectType::Trino)
25670                    | Some(DialectType::ClickHouse) => {
25671                        // These dialects use Array(TYPE) parentheses syntax
25672                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25673                            self.write("Array(");
25674                        } else {
25675                            self.write_keyword("ARRAY(");
25676                        }
25677                        self.generate_data_type(element_type)?;
25678                        self.write(")");
25679                    }
25680                    Some(DialectType::TSQL)
25681                    | Some(DialectType::MySQL)
25682                    | Some(DialectType::Oracle) => {
25683                        // These dialects don't have native array types
25684                        // Fall back to JSON or use native workarounds
25685                        match self.config.dialect {
25686                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
25687                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
25688                            _ => self.write_keyword("JSON"),
25689                        }
25690                    }
25691                    _ => {
25692                        // Default: use angle bracket syntax (ARRAY<T>)
25693                        self.write_keyword("ARRAY<");
25694                        self.generate_data_type(element_type)?;
25695                        self.write(">");
25696                    }
25697                }
25698            }
25699            DataType::List { element_type } => {
25700                // Materialize: element_type LIST (postfix syntax)
25701                self.generate_data_type(element_type)?;
25702                self.write_keyword(" LIST");
25703            }
25704            DataType::Map {
25705                key_type,
25706                value_type,
25707            } => {
25708                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
25709                match self.config.dialect {
25710                    Some(DialectType::Materialize) => {
25711                        // Materialize: MAP[key_type => value_type]
25712                        self.write_keyword("MAP[");
25713                        self.generate_data_type(key_type)?;
25714                        self.write(" => ");
25715                        self.generate_data_type(value_type)?;
25716                        self.write("]");
25717                    }
25718                    Some(DialectType::Snowflake)
25719                    | Some(DialectType::RisingWave)
25720                    | Some(DialectType::DuckDB)
25721                    | Some(DialectType::Presto)
25722                    | Some(DialectType::Trino)
25723                    | Some(DialectType::Athena) => {
25724                        self.write_keyword("MAP(");
25725                        self.generate_data_type(key_type)?;
25726                        self.write(", ");
25727                        self.generate_data_type(value_type)?;
25728                        self.write(")");
25729                    }
25730                    Some(DialectType::ClickHouse) => {
25731                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
25732                        // Key types must NOT be wrapped in Nullable
25733                        self.write("Map(");
25734                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
25735                        self.generate_data_type(key_type)?;
25736                        self.clickhouse_nullable_depth = 0;
25737                        self.write(", ");
25738                        self.generate_data_type(value_type)?;
25739                        self.write(")");
25740                    }
25741                    _ => {
25742                        self.write_keyword("MAP<");
25743                        self.generate_data_type(key_type)?;
25744                        self.write(", ");
25745                        self.generate_data_type(value_type)?;
25746                        self.write(">");
25747                    }
25748                }
25749            }
25750            DataType::Vector {
25751                element_type,
25752                dimension,
25753            } => {
25754                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
25755                    // SingleStore format: VECTOR(dimension, type_alias)
25756                    self.write_keyword("VECTOR(");
25757                    if let Some(dim) = dimension {
25758                        self.write(&dim.to_string());
25759                    }
25760                    // Map type back to SingleStore alias
25761                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
25762                        DataType::TinyInt { .. } => Some("I8"),
25763                        DataType::SmallInt { .. } => Some("I16"),
25764                        DataType::Int { .. } => Some("I32"),
25765                        DataType::BigInt { .. } => Some("I64"),
25766                        DataType::Float { .. } => Some("F32"),
25767                        DataType::Double { .. } => Some("F64"),
25768                        _ => None,
25769                    });
25770                    if let Some(alias) = type_alias {
25771                        if dimension.is_some() {
25772                            self.write(", ");
25773                        }
25774                        self.write(alias);
25775                    }
25776                    self.write(")");
25777                } else {
25778                    // Snowflake format: VECTOR(type, dimension)
25779                    self.write_keyword("VECTOR(");
25780                    if let Some(ref et) = element_type {
25781                        self.generate_data_type(et)?;
25782                        if dimension.is_some() {
25783                            self.write(", ");
25784                        }
25785                    }
25786                    if let Some(dim) = dimension {
25787                        self.write(&dim.to_string());
25788                    }
25789                    self.write(")");
25790                }
25791            }
25792            DataType::Object { fields, modifier } => {
25793                self.write_keyword("OBJECT(");
25794                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
25795                    if i > 0 {
25796                        self.write(", ");
25797                    }
25798                    self.write(name);
25799                    self.write(" ");
25800                    self.generate_data_type(dt)?;
25801                    if *not_null {
25802                        self.write_keyword(" NOT NULL");
25803                    }
25804                }
25805                self.write(")");
25806                if let Some(mod_str) = modifier {
25807                    self.write(" ");
25808                    self.write_keyword(mod_str);
25809                }
25810            }
25811            DataType::Struct { fields, nested } => {
25812                // Dialect-specific struct type mappings
25813                match self.config.dialect {
25814                    Some(DialectType::Snowflake) => {
25815                        // Snowflake maps STRUCT to OBJECT
25816                        self.write_keyword("OBJECT(");
25817                        for (i, field) in fields.iter().enumerate() {
25818                            if i > 0 {
25819                                self.write(", ");
25820                            }
25821                            if !field.name.is_empty() {
25822                                self.write(&field.name);
25823                                self.write(" ");
25824                            }
25825                            self.generate_data_type(&field.data_type)?;
25826                        }
25827                        self.write(")");
25828                    }
25829                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
25830                        // Presto/Trino use ROW(name TYPE, ...) syntax
25831                        self.write_keyword("ROW(");
25832                        for (i, field) in fields.iter().enumerate() {
25833                            if i > 0 {
25834                                self.write(", ");
25835                            }
25836                            if !field.name.is_empty() {
25837                                self.write(&field.name);
25838                                self.write(" ");
25839                            }
25840                            self.generate_data_type(&field.data_type)?;
25841                        }
25842                        self.write(")");
25843                    }
25844                    Some(DialectType::DuckDB) => {
25845                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
25846                        self.write_keyword("STRUCT(");
25847                        for (i, field) in fields.iter().enumerate() {
25848                            if i > 0 {
25849                                self.write(", ");
25850                            }
25851                            if !field.name.is_empty() {
25852                                self.write(&field.name);
25853                                self.write(" ");
25854                            }
25855                            self.generate_data_type(&field.data_type)?;
25856                        }
25857                        self.write(")");
25858                    }
25859                    Some(DialectType::ClickHouse) => {
25860                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
25861                        self.write("Tuple(");
25862                        for (i, field) in fields.iter().enumerate() {
25863                            if i > 0 {
25864                                self.write(", ");
25865                            }
25866                            if !field.name.is_empty() {
25867                                self.write(&field.name);
25868                                self.write(" ");
25869                            }
25870                            self.generate_data_type(&field.data_type)?;
25871                        }
25872                        self.write(")");
25873                    }
25874                    Some(DialectType::SingleStore) => {
25875                        // SingleStore uses RECORD(name TYPE, ...) for struct types
25876                        self.write_keyword("RECORD(");
25877                        for (i, field) in fields.iter().enumerate() {
25878                            if i > 0 {
25879                                self.write(", ");
25880                            }
25881                            if !field.name.is_empty() {
25882                                self.write(&field.name);
25883                                self.write(" ");
25884                            }
25885                            self.generate_data_type(&field.data_type)?;
25886                        }
25887                        self.write(")");
25888                    }
25889                    _ => {
25890                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
25891                        let force_angle_brackets = matches!(
25892                            self.config.dialect,
25893                            Some(DialectType::Hive)
25894                                | Some(DialectType::Spark)
25895                                | Some(DialectType::Databricks)
25896                        );
25897                        if *nested && !force_angle_brackets {
25898                            self.write_keyword("STRUCT(");
25899                            for (i, field) in fields.iter().enumerate() {
25900                                if i > 0 {
25901                                    self.write(", ");
25902                                }
25903                                if !field.name.is_empty() {
25904                                    self.write(&field.name);
25905                                    self.write(" ");
25906                                }
25907                                self.generate_data_type(&field.data_type)?;
25908                            }
25909                            self.write(")");
25910                        } else {
25911                            self.write_keyword("STRUCT<");
25912                            for (i, field) in fields.iter().enumerate() {
25913                                if i > 0 {
25914                                    self.write(", ");
25915                                }
25916                                if !field.name.is_empty() {
25917                                    // Named field: name TYPE (with configurable separator for Hive)
25918                                    self.write(&field.name);
25919                                    self.write(self.config.struct_field_sep);
25920                                }
25921                                // For anonymous fields, just output the type
25922                                self.generate_data_type(&field.data_type)?;
25923                                // Spark/Databricks: Output COMMENT clause if present
25924                                if let Some(comment) = &field.comment {
25925                                    self.write(" COMMENT '");
25926                                    self.write(comment);
25927                                    self.write("'");
25928                                }
25929                                // BigQuery: Output OPTIONS clause if present
25930                                if !field.options.is_empty() {
25931                                    self.write(" ");
25932                                    self.generate_options_clause(&field.options)?;
25933                                }
25934                            }
25935                            self.write(">");
25936                        }
25937                    }
25938                }
25939            }
25940            DataType::Enum {
25941                values,
25942                assignments,
25943            } => {
25944                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
25945                // ClickHouse: Enum('hello' = 1, 'world' = 2)
25946                if self.config.dialect == Some(DialectType::ClickHouse) {
25947                    self.write("Enum(");
25948                } else {
25949                    self.write_keyword("ENUM(");
25950                }
25951                for (i, val) in values.iter().enumerate() {
25952                    if i > 0 {
25953                        self.write(", ");
25954                    }
25955                    self.write("'");
25956                    self.write(val);
25957                    self.write("'");
25958                    if let Some(Some(assignment)) = assignments.get(i) {
25959                        self.write(" = ");
25960                        self.write(assignment);
25961                    }
25962                }
25963                self.write(")");
25964            }
25965            DataType::Set { values } => {
25966                // MySQL SET type: SET('a', 'b', 'c')
25967                self.write_keyword("SET(");
25968                for (i, val) in values.iter().enumerate() {
25969                    if i > 0 {
25970                        self.write(", ");
25971                    }
25972                    self.write("'");
25973                    self.write(val);
25974                    self.write("'");
25975                }
25976                self.write(")");
25977            }
25978            DataType::Union { fields } => {
25979                // DuckDB UNION type: UNION(num INT, str TEXT)
25980                self.write_keyword("UNION(");
25981                for (i, (name, dt)) in fields.iter().enumerate() {
25982                    if i > 0 {
25983                        self.write(", ");
25984                    }
25985                    if !name.is_empty() {
25986                        self.write(name);
25987                        self.write(" ");
25988                    }
25989                    self.generate_data_type(dt)?;
25990                }
25991                self.write(")");
25992            }
25993            DataType::Nullable { inner } => {
25994                // ClickHouse: Nullable(T), other dialects: just the inner type
25995                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
25996                    self.write("Nullable(");
25997                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
25998                    let saved_depth = self.clickhouse_nullable_depth;
25999                    self.clickhouse_nullable_depth = -1;
26000                    self.generate_data_type(inner)?;
26001                    self.clickhouse_nullable_depth = saved_depth;
26002                    self.write(")");
26003                } else {
26004                    // Map ClickHouse-specific custom type names to standard types
26005                    match inner.as_ref() {
26006                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
26007                            self.generate_data_type(&DataType::Timestamp {
26008                                precision: None,
26009                                timezone: false,
26010                            })?;
26011                        }
26012                        _ => {
26013                            self.generate_data_type(inner)?;
26014                        }
26015                    }
26016                }
26017            }
26018            DataType::Custom { name } => {
26019                // Handle dialect-specific type transformations
26020                let name_upper = name.to_ascii_uppercase();
26021                match self.config.dialect {
26022                    Some(DialectType::ClickHouse) => {
26023                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
26024                            (name_upper[..idx].to_string(), &name[idx..])
26025                        } else {
26026                            (name_upper.clone(), "")
26027                        };
26028                        let mapped = match base_upper.as_str() {
26029                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
26030                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
26031                            "DATETIME64" => "DateTime64",
26032                            "DATE32" => "Date32",
26033                            "INT" => "Int32",
26034                            "MEDIUMINT" => "Int32",
26035                            "INT8" => "Int8",
26036                            "INT16" => "Int16",
26037                            "INT32" => "Int32",
26038                            "INT64" => "Int64",
26039                            "INT128" => "Int128",
26040                            "INT256" => "Int256",
26041                            "UINT8" => "UInt8",
26042                            "UINT16" => "UInt16",
26043                            "UINT32" => "UInt32",
26044                            "UINT64" => "UInt64",
26045                            "UINT128" => "UInt128",
26046                            "UINT256" => "UInt256",
26047                            "FLOAT32" => "Float32",
26048                            "FLOAT64" => "Float64",
26049                            "DECIMAL32" => "Decimal32",
26050                            "DECIMAL64" => "Decimal64",
26051                            "DECIMAL128" => "Decimal128",
26052                            "DECIMAL256" => "Decimal256",
26053                            "ENUM" => "Enum",
26054                            "ENUM8" => "Enum8",
26055                            "ENUM16" => "Enum16",
26056                            "FIXEDSTRING" => "FixedString",
26057                            "NESTED" => "Nested",
26058                            "LOWCARDINALITY" => "LowCardinality",
26059                            "NULLABLE" => "Nullable",
26060                            "IPV4" => "IPv4",
26061                            "IPV6" => "IPv6",
26062                            "POINT" => "Point",
26063                            "RING" => "Ring",
26064                            "LINESTRING" => "LineString",
26065                            "MULTILINESTRING" => "MultiLineString",
26066                            "POLYGON" => "Polygon",
26067                            "MULTIPOLYGON" => "MultiPolygon",
26068                            "AGGREGATEFUNCTION" => "AggregateFunction",
26069                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
26070                            "DYNAMIC" => "Dynamic",
26071                            _ => "",
26072                        };
26073                        if mapped.is_empty() {
26074                            self.write(name);
26075                        } else {
26076                            self.write(mapped);
26077                            if matches!(base_upper.as_str(), "ENUM8" | "ENUM16")
26078                                && !suffix.is_empty()
26079                            {
26080                                let escaped_suffix = suffix
26081                                    .replace('\\', "\\\\")
26082                                    .replace('\t', "\\t")
26083                                    .replace('\n', "\\n")
26084                                    .replace('\r', "\\r");
26085                                self.write(&escaped_suffix);
26086                            } else {
26087                                self.write(suffix);
26088                            }
26089                        }
26090                    }
26091                    Some(DialectType::MySQL)
26092                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
26093                    {
26094                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
26095                        self.write_keyword("TIMESTAMP");
26096                    }
26097                    Some(DialectType::Snowflake) => {
26098                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
26099                            (name_upper[..idx].to_string(), &name[idx..])
26100                        } else {
26101                            (name_upper.clone(), "")
26102                        };
26103
26104                        match base_upper.as_str() {
26105                            "TIMESTAMPNTZ" | "TIMESTAMP_NTZ" => {
26106                                self.write_keyword("TIMESTAMPNTZ");
26107                                self.write(suffix);
26108                            }
26109                            "TIMESTAMPLTZ" | "TIMESTAMP_LTZ" => {
26110                                self.write_keyword("TIMESTAMPLTZ");
26111                                self.write(suffix);
26112                            }
26113                            "TIMESTAMPTZ" | "TIMESTAMP_TZ" => {
26114                                self.write_keyword("TIMESTAMPTZ");
26115                                self.write(suffix);
26116                            }
26117                            _ => self.write(name),
26118                        }
26119                    }
26120                    Some(DialectType::Fabric) => {
26121                        let (base_upper, args_str) = if let Some(idx) = name.find('(') {
26122                            (name_upper[..idx].to_string(), Some(&name[idx..]))
26123                        } else {
26124                            (name_upper.clone(), None)
26125                        };
26126
26127                        match base_upper.as_str() {
26128                            "NVARCHAR" => {
26129                                self.write_keyword("VARCHAR");
26130                                if let Some(args) = args_str {
26131                                    self.write(args);
26132                                }
26133                            }
26134                            "NCHAR" => {
26135                                self.write_keyword("CHAR");
26136                                if let Some(args) = args_str {
26137                                    self.write(args);
26138                                }
26139                            }
26140                            _ => self.write(name),
26141                        }
26142                    }
26143                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
26144                        self.write_keyword("SQL_VARIANT");
26145                    }
26146                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
26147                        self.write_keyword("DECIMAL(38, 5)");
26148                    }
26149                    Some(DialectType::Exasol) => {
26150                        // Exasol type mappings for custom types
26151                        match name_upper.as_str() {
26152                            // Binary types → VARCHAR
26153                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
26154                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
26155                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
26156                            // Integer types
26157                            "MEDIUMINT" => self.write_keyword("INT"),
26158                            // Decimal types → DECIMAL
26159                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
26160                                self.write_keyword("DECIMAL")
26161                            }
26162                            // Timestamp types
26163                            "DATETIME" => self.write_keyword("TIMESTAMP"),
26164                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
26165                            _ => self.write(name),
26166                        }
26167                    }
26168                    Some(DialectType::Dremio) => {
26169                        // Dremio type mappings for custom types
26170                        match name_upper.as_str() {
26171                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
26172                            "ARRAY" => self.write_keyword("LIST"),
26173                            "NCHAR" => self.write_keyword("VARCHAR"),
26174                            _ => self.write(name),
26175                        }
26176                    }
26177                    // Map dialect-specific custom types to standard SQL types for other dialects
26178                    _ => {
26179                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
26180                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
26181                            (name_upper[..idx].to_string(), Some(&name[idx..]))
26182                        } else {
26183                            (name_upper.clone(), None)
26184                        };
26185
26186                        match base_upper.as_str() {
26187                            "INT64"
26188                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
26189                            {
26190                                self.write_keyword("BIGINT");
26191                            }
26192                            "FLOAT64"
26193                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
26194                            {
26195                                self.write_keyword("DOUBLE");
26196                            }
26197                            "BOOL"
26198                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
26199                            {
26200                                self.write_keyword("BOOLEAN");
26201                            }
26202                            "BYTES"
26203                                if matches!(
26204                                    self.config.dialect,
26205                                    Some(DialectType::Spark)
26206                                        | Some(DialectType::Hive)
26207                                        | Some(DialectType::Databricks)
26208                                ) =>
26209                            {
26210                                self.write_keyword("BINARY");
26211                            }
26212                            "BYTES"
26213                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
26214                            {
26215                                self.write_keyword("VARBINARY");
26216                            }
26217                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
26218                            "DATETIME2" | "SMALLDATETIME"
26219                                if !matches!(
26220                                    self.config.dialect,
26221                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26222                                ) =>
26223                            {
26224                                // PostgreSQL preserves precision, others don't
26225                                if matches!(
26226                                    self.config.dialect,
26227                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
26228                                ) {
26229                                    self.write_keyword("TIMESTAMP");
26230                                    if let Some(args) = _args_str {
26231                                        self.write(args);
26232                                    }
26233                                } else {
26234                                    self.write_keyword("TIMESTAMP");
26235                                }
26236                            }
26237                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
26238                            "DATETIMEOFFSET"
26239                                if !matches!(
26240                                    self.config.dialect,
26241                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26242                                ) =>
26243                            {
26244                                if matches!(
26245                                    self.config.dialect,
26246                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
26247                                ) {
26248                                    self.write_keyword("TIMESTAMPTZ");
26249                                    if let Some(args) = _args_str {
26250                                        self.write(args);
26251                                    }
26252                                } else {
26253                                    self.write_keyword("TIMESTAMPTZ");
26254                                }
26255                            }
26256                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
26257                            "UNIQUEIDENTIFIER"
26258                                if !matches!(
26259                                    self.config.dialect,
26260                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26261                                ) =>
26262                            {
26263                                match self.config.dialect {
26264                                    Some(DialectType::Spark)
26265                                    | Some(DialectType::Databricks)
26266                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
26267                                    _ => self.write_keyword("UUID"),
26268                                }
26269                            }
26270                            // TSQL BIT -> BOOLEAN for most dialects
26271                            "BIT"
26272                                if !matches!(
26273                                    self.config.dialect,
26274                                    Some(DialectType::TSQL)
26275                                        | Some(DialectType::Fabric)
26276                                        | Some(DialectType::PostgreSQL)
26277                                        | Some(DialectType::MySQL)
26278                                        | Some(DialectType::DuckDB)
26279                                ) =>
26280                            {
26281                                self.write_keyword("BOOLEAN");
26282                            }
26283                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
26284                            "NVARCHAR"
26285                                if !matches!(
26286                                    self.config.dialect,
26287                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26288                                ) =>
26289                            {
26290                                match self.config.dialect {
26291                                    Some(DialectType::Oracle) => {
26292                                        // Oracle: NVARCHAR -> NVARCHAR2
26293                                        self.write_keyword("NVARCHAR2");
26294                                        if let Some(args) = _args_str {
26295                                            self.write(args);
26296                                        }
26297                                    }
26298                                    Some(DialectType::BigQuery) => {
26299                                        // BigQuery: NVARCHAR -> STRING
26300                                        self.write_keyword("STRING");
26301                                    }
26302                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
26303                                        self.write_keyword("TEXT");
26304                                        if let Some(args) = _args_str {
26305                                            self.write(args);
26306                                        }
26307                                    }
26308                                    Some(DialectType::Hive) => {
26309                                        // Hive: NVARCHAR -> STRING
26310                                        self.write_keyword("STRING");
26311                                    }
26312                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
26313                                        if _args_str.is_some() {
26314                                            self.write_keyword("VARCHAR");
26315                                            self.write(_args_str.unwrap());
26316                                        } else {
26317                                            self.write_keyword("STRING");
26318                                        }
26319                                    }
26320                                    _ => {
26321                                        self.write_keyword("VARCHAR");
26322                                        if let Some(args) = _args_str {
26323                                            self.write(args);
26324                                        }
26325                                    }
26326                                }
26327                            }
26328                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
26329                            "NCHAR"
26330                                if !matches!(
26331                                    self.config.dialect,
26332                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
26333                                ) =>
26334                            {
26335                                match self.config.dialect {
26336                                    Some(DialectType::Oracle) => {
26337                                        // Oracle natively supports NCHAR
26338                                        self.write_keyword("NCHAR");
26339                                        if let Some(args) = _args_str {
26340                                            self.write(args);
26341                                        }
26342                                    }
26343                                    Some(DialectType::BigQuery) => {
26344                                        // BigQuery: NCHAR -> STRING
26345                                        self.write_keyword("STRING");
26346                                    }
26347                                    Some(DialectType::Hive) => {
26348                                        // Hive: NCHAR -> STRING
26349                                        self.write_keyword("STRING");
26350                                    }
26351                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
26352                                        self.write_keyword("TEXT");
26353                                        if let Some(args) = _args_str {
26354                                            self.write(args);
26355                                        }
26356                                    }
26357                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
26358                                        if _args_str.is_some() {
26359                                            self.write_keyword("CHAR");
26360                                            self.write(_args_str.unwrap());
26361                                        } else {
26362                                            self.write_keyword("STRING");
26363                                        }
26364                                    }
26365                                    _ => {
26366                                        self.write_keyword("CHAR");
26367                                        if let Some(args) = _args_str {
26368                                            self.write(args);
26369                                        }
26370                                    }
26371                                }
26372                            }
26373                            // MySQL text variant types -> map to appropriate target type
26374                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
26375                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
26376                                Some(DialectType::MySQL)
26377                                | Some(DialectType::SingleStore)
26378                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
26379                                Some(DialectType::Spark)
26380                                | Some(DialectType::Databricks)
26381                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
26382                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
26383                                Some(DialectType::Presto)
26384                                | Some(DialectType::Trino)
26385                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
26386                                Some(DialectType::Snowflake)
26387                                | Some(DialectType::Redshift)
26388                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
26389                                _ => self.write_keyword("TEXT"),
26390                            },
26391                            // MySQL blob variant types -> map to appropriate target type
26392                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
26393                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
26394                                Some(DialectType::MySQL)
26395                                | Some(DialectType::SingleStore)
26396                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
26397                                Some(DialectType::Spark)
26398                                | Some(DialectType::Databricks)
26399                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
26400                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
26401                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
26402                                Some(DialectType::Presto)
26403                                | Some(DialectType::Trino)
26404                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
26405                                Some(DialectType::Snowflake)
26406                                | Some(DialectType::Redshift)
26407                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
26408                                _ => self.write_keyword("BLOB"),
26409                            },
26410                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
26411                            "LONGVARCHAR" => match self.config.dialect {
26412                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
26413                                _ => self.write_keyword("VARCHAR"),
26414                            },
26415                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
26416                            "DATETIME" => {
26417                                match self.config.dialect {
26418                                    Some(DialectType::MySQL)
26419                                    | Some(DialectType::Doris)
26420                                    | Some(DialectType::StarRocks)
26421                                    | Some(DialectType::TSQL)
26422                                    | Some(DialectType::Fabric)
26423                                    | Some(DialectType::BigQuery)
26424                                    | Some(DialectType::SQLite)
26425                                    | Some(DialectType::Snowflake) => {
26426                                        self.write_keyword("DATETIME");
26427                                        if let Some(args) = _args_str {
26428                                            self.write(args);
26429                                        }
26430                                    }
26431                                    Some(_) => {
26432                                        // Only map to TIMESTAMP when we have a specific target dialect
26433                                        self.write_keyword("TIMESTAMP");
26434                                        if let Some(args) = _args_str {
26435                                            self.write(args);
26436                                        }
26437                                    }
26438                                    None => {
26439                                        // No dialect - preserve original
26440                                        self.write(name);
26441                                    }
26442                                }
26443                            }
26444                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
26445                            "VARCHAR2"
26446                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
26447                            {
26448                                match self.config.dialect {
26449                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
26450                                        self.write_keyword("TEXT");
26451                                    }
26452                                    Some(DialectType::Hive)
26453                                    | Some(DialectType::Spark)
26454                                    | Some(DialectType::Databricks)
26455                                    | Some(DialectType::BigQuery)
26456                                    | Some(DialectType::ClickHouse)
26457                                    | Some(DialectType::StarRocks)
26458                                    | Some(DialectType::Doris) => {
26459                                        self.write_keyword("STRING");
26460                                    }
26461                                    _ => {
26462                                        self.write_keyword("VARCHAR");
26463                                        if let Some(args) = _args_str {
26464                                            self.write(args);
26465                                        }
26466                                    }
26467                                }
26468                            }
26469                            "NVARCHAR2"
26470                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
26471                            {
26472                                match self.config.dialect {
26473                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
26474                                        self.write_keyword("TEXT");
26475                                    }
26476                                    Some(DialectType::Hive)
26477                                    | Some(DialectType::Spark)
26478                                    | Some(DialectType::Databricks)
26479                                    | Some(DialectType::BigQuery)
26480                                    | Some(DialectType::ClickHouse)
26481                                    | Some(DialectType::StarRocks)
26482                                    | Some(DialectType::Doris) => {
26483                                        self.write_keyword("STRING");
26484                                    }
26485                                    _ => {
26486                                        self.write_keyword("VARCHAR");
26487                                        if let Some(args) = _args_str {
26488                                            self.write(args);
26489                                        }
26490                                    }
26491                                }
26492                            }
26493                            _ => self.write(name),
26494                        }
26495                    }
26496                }
26497            }
26498            DataType::Geometry { subtype, srid } => {
26499                // Dialect-specific geometry type mappings
26500                match self.config.dialect {
26501                    Some(DialectType::MySQL) => {
26502                        // MySQL uses POINT SRID 4326 syntax for specific types
26503                        if let Some(sub) = subtype {
26504                            self.write_keyword(sub);
26505                            if let Some(s) = srid {
26506                                self.write(" SRID ");
26507                                self.write(&s.to_string());
26508                            }
26509                        } else {
26510                            self.write_keyword("GEOMETRY");
26511                        }
26512                    }
26513                    Some(DialectType::BigQuery) => {
26514                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
26515                        self.write_keyword("GEOGRAPHY");
26516                    }
26517                    Some(DialectType::Teradata) => {
26518                        // Teradata uses ST_GEOMETRY
26519                        self.write_keyword("ST_GEOMETRY");
26520                        if subtype.is_some() || srid.is_some() {
26521                            self.write("(");
26522                            if let Some(sub) = subtype {
26523                                self.write_keyword(sub);
26524                            }
26525                            if let Some(s) = srid {
26526                                if subtype.is_some() {
26527                                    self.write(", ");
26528                                }
26529                                self.write(&s.to_string());
26530                            }
26531                            self.write(")");
26532                        }
26533                    }
26534                    _ => {
26535                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
26536                        self.write_keyword("GEOMETRY");
26537                        if subtype.is_some() || srid.is_some() {
26538                            self.write("(");
26539                            if let Some(sub) = subtype {
26540                                self.write_keyword(sub);
26541                            }
26542                            if let Some(s) = srid {
26543                                if subtype.is_some() {
26544                                    self.write(", ");
26545                                }
26546                                self.write(&s.to_string());
26547                            }
26548                            self.write(")");
26549                        }
26550                    }
26551                }
26552            }
26553            DataType::Geography { subtype, srid } => {
26554                // Dialect-specific geography type mappings
26555                match self.config.dialect {
26556                    Some(DialectType::MySQL) => {
26557                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
26558                        if let Some(sub) = subtype {
26559                            self.write_keyword(sub);
26560                        } else {
26561                            self.write_keyword("GEOMETRY");
26562                        }
26563                        // Geography implies SRID 4326 (WGS84)
26564                        let effective_srid = srid.unwrap_or(4326);
26565                        self.write(" SRID ");
26566                        self.write(&effective_srid.to_string());
26567                    }
26568                    Some(DialectType::BigQuery) => {
26569                        // BigQuery uses simple GEOGRAPHY without parameters
26570                        self.write_keyword("GEOGRAPHY");
26571                    }
26572                    Some(DialectType::Snowflake) => {
26573                        // Snowflake uses GEOGRAPHY without parameters
26574                        self.write_keyword("GEOGRAPHY");
26575                    }
26576                    _ => {
26577                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
26578                        self.write_keyword("GEOGRAPHY");
26579                        if subtype.is_some() || srid.is_some() {
26580                            self.write("(");
26581                            if let Some(sub) = subtype {
26582                                self.write_keyword(sub);
26583                            }
26584                            if let Some(s) = srid {
26585                                if subtype.is_some() {
26586                                    self.write(", ");
26587                                }
26588                                self.write(&s.to_string());
26589                            }
26590                            self.write(")");
26591                        }
26592                    }
26593                }
26594            }
26595            DataType::CharacterSet { name } => {
26596                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
26597                self.write_keyword("CHAR CHARACTER SET ");
26598                self.write(name);
26599            }
26600            _ => self.write("UNKNOWN"),
26601        }
26602        Ok(())
26603    }
26604
26605    // === Helper methods ===
26606
26607    #[inline]
26608    fn write(&mut self, s: &str) {
26609        self.output.push_str(s);
26610    }
26611
26612    #[inline]
26613    fn write_space(&mut self) {
26614        self.output.push(' ');
26615    }
26616
26617    #[inline]
26618    fn write_keyword(&mut self, keyword: &str) {
26619        if self.config.uppercase_keywords {
26620            self.output.push_str(keyword);
26621        } else {
26622            for b in keyword.bytes() {
26623                self.output.push(b.to_ascii_lowercase() as char);
26624            }
26625        }
26626    }
26627
26628    /// Write a function name respecting the normalize_functions config setting
26629    fn write_func_name(&mut self, name: &str) {
26630        let normalized = self.normalize_func_name(name);
26631        self.output.push_str(normalized.as_ref());
26632    }
26633
26634    /// Convert strptime format string to Exasol format string
26635    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
26636    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
26637    fn convert_strptime_to_exasol_format(format: &str) -> String {
26638        let mut result = String::new();
26639        let chars: Vec<char> = format.chars().collect();
26640        let mut i = 0;
26641        while i < chars.len() {
26642            if chars[i] == '%' && i + 1 < chars.len() {
26643                let spec = chars[i + 1];
26644                let exasol_spec = match spec {
26645                    'Y' => "YYYY",
26646                    'y' => "YY",
26647                    'm' => "MM",
26648                    'd' => "DD",
26649                    'H' => "HH",
26650                    'M' => "MI",
26651                    'S' => "SS",
26652                    'a' => "DY",    // abbreviated weekday name
26653                    'A' => "DAY",   // full weekday name
26654                    'b' => "MON",   // abbreviated month name
26655                    'B' => "MONTH", // full month name
26656                    'I' => "H12",   // 12-hour format
26657                    'u' => "ID",    // ISO weekday (1-7)
26658                    'V' => "IW",    // ISO week number
26659                    'G' => "IYYY",  // ISO year
26660                    'W' => "UW",    // Week number (Monday as first day)
26661                    'U' => "UW",    // Week number (Sunday as first day)
26662                    'z' => "Z",     // timezone offset
26663                    _ => {
26664                        // Unknown specifier, keep as-is
26665                        result.push('%');
26666                        result.push(spec);
26667                        i += 2;
26668                        continue;
26669                    }
26670                };
26671                result.push_str(exasol_spec);
26672                i += 2;
26673            } else {
26674                result.push(chars[i]);
26675                i += 1;
26676            }
26677        }
26678        result
26679    }
26680
26681    /// Convert strptime format string to PostgreSQL/Redshift format string
26682    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
26683    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
26684    fn convert_strptime_to_postgres_format(format: &str) -> String {
26685        let mut result = String::new();
26686        let chars: Vec<char> = format.chars().collect();
26687        let mut i = 0;
26688        while i < chars.len() {
26689            if chars[i] == '%' && i + 1 < chars.len() {
26690                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
26691                if chars[i + 1] == '-' && i + 2 < chars.len() {
26692                    let spec = chars[i + 2];
26693                    let pg_spec = match spec {
26694                        'd' => "FMDD",
26695                        'm' => "FMMM",
26696                        'H' => "FMHH24",
26697                        'M' => "FMMI",
26698                        'S' => "FMSS",
26699                        _ => {
26700                            result.push('%');
26701                            result.push('-');
26702                            result.push(spec);
26703                            i += 3;
26704                            continue;
26705                        }
26706                    };
26707                    result.push_str(pg_spec);
26708                    i += 3;
26709                    continue;
26710                }
26711                let spec = chars[i + 1];
26712                let pg_spec = match spec {
26713                    'Y' => "YYYY",
26714                    'y' => "YY",
26715                    'm' => "MM",
26716                    'd' => "DD",
26717                    'H' => "HH24",
26718                    'I' => "HH12",
26719                    'M' => "MI",
26720                    'S' => "SS",
26721                    'f' => "US",      // microseconds
26722                    'u' => "D",       // day of week (1=Monday)
26723                    'j' => "DDD",     // day of year
26724                    'z' => "OF",      // UTC offset
26725                    'Z' => "TZ",      // timezone name
26726                    'A' => "TMDay",   // full weekday name
26727                    'a' => "TMDy",    // abbreviated weekday name
26728                    'b' => "TMMon",   // abbreviated month name
26729                    'B' => "TMMonth", // full month name
26730                    'U' => "WW",      // week number
26731                    _ => {
26732                        // Unknown specifier, keep as-is
26733                        result.push('%');
26734                        result.push(spec);
26735                        i += 2;
26736                        continue;
26737                    }
26738                };
26739                result.push_str(pg_spec);
26740                i += 2;
26741            } else {
26742                result.push(chars[i]);
26743                i += 1;
26744            }
26745        }
26746        result
26747    }
26748
26749    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
26750    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
26751        if self.config.limit_only_literals {
26752            if let Some(value) = Self::try_evaluate_constant(expr) {
26753                self.write(&value.to_string());
26754                return Ok(());
26755            }
26756        }
26757        self.generate_expression(expr)
26758    }
26759
26760    fn is_noop_limit_expr(expr: &Expression) -> bool {
26761        match expr {
26762            Expression::Null(_) => true,
26763            Expression::Identifier(identifier) => identifier.name.eq_ignore_ascii_case("ALL"),
26764            Expression::Column(column) => {
26765                column.table.is_none() && column.name.name.eq_ignore_ascii_case("ALL")
26766            }
26767            Expression::Var(var) => var.this.eq_ignore_ascii_case("ALL"),
26768            Expression::Paren(paren) => Self::is_noop_limit_expr(&paren.this),
26769            _ => false,
26770        }
26771    }
26772
26773    /// Format a comment with proper spacing.
26774    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
26775    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
26776    fn write_formatted_comment(&mut self, comment: &str) {
26777        // Normalize all comments to block comment format /* ... */
26778        // This matches Python sqlglot behavior which always outputs block comments
26779        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
26780            // Already block comment - extract inner content
26781            // Preserve internal whitespace, but ensure at least one space padding
26782            &comment[2..comment.len() - 2]
26783        } else if comment.starts_with("--") {
26784            // Line comment - extract content after --
26785            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
26786            &comment[2..]
26787        } else {
26788            // Raw content (no delimiters)
26789            comment
26790        };
26791        // Skip empty comments (e.g., bare "--" with no content)
26792        if content.trim().is_empty() {
26793            return;
26794        }
26795        // Escape nested block comment markers to prevent premature closure or unintended nesting.
26796        // This matches Python sqlglot's sanitize_comment behavior.
26797        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
26798        let content = &sanitized;
26799        // Ensure at least one space after /* and before */
26800        self.output.push_str("/*");
26801        if !content.starts_with(' ') {
26802            self.output.push(' ');
26803        }
26804        self.output.push_str(content);
26805        if !content.ends_with(' ') {
26806            self.output.push(' ');
26807        }
26808        self.output.push_str("*/");
26809    }
26810
26811    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
26812    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
26813    fn escape_block_for_single_quote(&self, block: &str) -> String {
26814        let escape_backslash = matches!(
26815            self.config.dialect,
26816            Some(crate::dialects::DialectType::Snowflake)
26817        );
26818        let mut escaped = String::with_capacity(block.len() + 4);
26819        for ch in block.chars() {
26820            if ch == '\'' {
26821                escaped.push('\\');
26822                escaped.push('\'');
26823            } else if escape_backslash && ch == '\\' {
26824                escaped.push('\\');
26825                escaped.push('\\');
26826            } else {
26827                escaped.push(ch);
26828            }
26829        }
26830        escaped
26831    }
26832
26833    fn write_newline(&mut self) {
26834        self.output.push('\n');
26835    }
26836
26837    fn write_indent(&mut self) {
26838        for _ in 0..self.indent_level {
26839            self.output.push_str(self.config.indent);
26840        }
26841    }
26842
26843    // === SQLGlot-style pretty printing helpers ===
26844
26845    /// Returns the separator string for pretty printing.
26846    /// Check if the total length of arguments exceeds max_text_width.
26847    /// Used for dynamic line breaking in expressions() formatting.
26848    fn too_wide(&self, args: &[String]) -> bool {
26849        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
26850    }
26851
26852    /// Generate an expression to a string using a temporary non-pretty generator.
26853    /// Useful for width calculations before deciding on formatting.
26854    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
26855        let config = GeneratorConfig {
26856            pretty: false,
26857            dialect: self.config.dialect,
26858            ..Default::default()
26859        };
26860        let mut gen = Generator::with_config(config);
26861        gen.generate_expression(expr)?;
26862        Ok(gen.output)
26863    }
26864
26865    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
26866    /// In pretty mode: newline + indented keyword + newline + indented condition
26867    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
26868        if self.config.pretty {
26869            self.write_newline();
26870            self.write_indent();
26871            self.write_keyword(keyword);
26872            self.write_newline();
26873            self.indent_level += 1;
26874            self.write_indent();
26875            self.generate_expression(condition)?;
26876            self.indent_level -= 1;
26877        } else {
26878            self.write_space();
26879            self.write_keyword(keyword);
26880            self.write_space();
26881            self.generate_expression(condition)?;
26882        }
26883        Ok(())
26884    }
26885
26886    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
26887    /// In pretty mode: each expression on new line with indentation
26888    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
26889        if exprs.is_empty() {
26890            return Ok(());
26891        }
26892
26893        if self.config.pretty {
26894            self.write_newline();
26895            self.write_indent();
26896            self.write_keyword(keyword);
26897            self.write_newline();
26898            self.indent_level += 1;
26899            for (i, expr) in exprs.iter().enumerate() {
26900                if i > 0 {
26901                    self.write(",");
26902                    self.write_newline();
26903                }
26904                self.write_indent();
26905                self.generate_expression(expr)?;
26906            }
26907            self.indent_level -= 1;
26908        } else {
26909            self.write_space();
26910            self.write_keyword(keyword);
26911            self.write_space();
26912            for (i, expr) in exprs.iter().enumerate() {
26913                if i > 0 {
26914                    self.write(", ");
26915                }
26916                self.generate_expression(expr)?;
26917            }
26918        }
26919        Ok(())
26920    }
26921
26922    /// Writes ORDER BY / SORT BY clause with Ordered expressions
26923    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
26924        if orderings.is_empty() {
26925            return Ok(());
26926        }
26927
26928        if self.config.pretty {
26929            self.write_newline();
26930            self.write_indent();
26931            self.write_keyword(keyword);
26932            self.write_newline();
26933            self.indent_level += 1;
26934            for (i, ordered) in orderings.iter().enumerate() {
26935                if i > 0 {
26936                    self.write(",");
26937                    self.write_newline();
26938                }
26939                self.write_indent();
26940                self.generate_ordered(ordered)?;
26941            }
26942            self.indent_level -= 1;
26943        } else {
26944            self.write_space();
26945            self.write_keyword(keyword);
26946            self.write_space();
26947            for (i, ordered) in orderings.iter().enumerate() {
26948                if i > 0 {
26949                    self.write(", ");
26950                }
26951                self.generate_ordered(ordered)?;
26952            }
26953        }
26954        Ok(())
26955    }
26956
26957    /// Writes WINDOW clause with named window definitions
26958    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
26959        if windows.is_empty() {
26960            return Ok(());
26961        }
26962
26963        if self.config.pretty {
26964            self.write_newline();
26965            self.write_indent();
26966            self.write_keyword("WINDOW");
26967            self.write_newline();
26968            self.indent_level += 1;
26969            for (i, named_window) in windows.iter().enumerate() {
26970                if i > 0 {
26971                    self.write(",");
26972                    self.write_newline();
26973                }
26974                self.write_indent();
26975                self.generate_identifier(&named_window.name)?;
26976                self.write_space();
26977                self.write_keyword("AS");
26978                self.write(" (");
26979                self.generate_over(&named_window.spec)?;
26980                self.write(")");
26981            }
26982            self.indent_level -= 1;
26983        } else {
26984            self.write_space();
26985            self.write_keyword("WINDOW");
26986            self.write_space();
26987            for (i, named_window) in windows.iter().enumerate() {
26988                if i > 0 {
26989                    self.write(", ");
26990                }
26991                self.generate_identifier(&named_window.name)?;
26992                self.write_space();
26993                self.write_keyword("AS");
26994                self.write(" (");
26995                self.generate_over(&named_window.spec)?;
26996                self.write(")");
26997            }
26998        }
26999        Ok(())
27000    }
27001
27002    // === BATCH-GENERATED STUB METHODS (481 variants) ===
27003    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
27004        // AI_AGG(this, expression)
27005        self.write_keyword("AI_AGG");
27006        self.write("(");
27007        self.generate_expression(&e.this)?;
27008        self.write(", ");
27009        self.generate_expression(&e.expression)?;
27010        self.write(")");
27011        Ok(())
27012    }
27013
27014    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
27015        // AI_CLASSIFY(input, [categories], [config])
27016        self.write_keyword("AI_CLASSIFY");
27017        self.write("(");
27018        self.generate_expression(&e.this)?;
27019        if let Some(categories) = &e.categories {
27020            self.write(", ");
27021            self.generate_expression(categories)?;
27022        }
27023        if let Some(config) = &e.config {
27024            self.write(", ");
27025            self.generate_expression(config)?;
27026        }
27027        self.write(")");
27028        Ok(())
27029    }
27030
27031    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
27032        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
27033        self.write_keyword("ADD");
27034        self.write_space();
27035        if e.exists {
27036            self.write_keyword("IF NOT EXISTS");
27037            self.write_space();
27038        }
27039        self.generate_expression(&e.this)?;
27040        if let Some(location) = &e.location {
27041            self.write_space();
27042            self.generate_expression(location)?;
27043        }
27044        Ok(())
27045    }
27046
27047    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
27048        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
27049        self.write_keyword("ALGORITHM");
27050        self.write("=");
27051        self.generate_expression(&e.this)?;
27052        Ok(())
27053    }
27054
27055    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
27056        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
27057        self.generate_expression(&e.this)?;
27058        self.write_space();
27059        self.write_keyword("AS");
27060        self.write(" (");
27061        for (i, expr) in e.expressions.iter().enumerate() {
27062            if i > 0 {
27063                self.write(", ");
27064            }
27065            self.generate_expression(expr)?;
27066        }
27067        self.write(")");
27068        Ok(())
27069    }
27070
27071    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
27072        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
27073        self.write_keyword("ALLOWED_VALUES");
27074        self.write_space();
27075        for (i, expr) in e.expressions.iter().enumerate() {
27076            if i > 0 {
27077                self.write(", ");
27078            }
27079            self.generate_expression(expr)?;
27080        }
27081        Ok(())
27082    }
27083
27084    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
27085        // Python: complex logic based on dtype, default, comment, visible, etc.
27086        self.write_keyword("ALTER COLUMN");
27087        self.write_space();
27088        self.generate_expression(&e.this)?;
27089
27090        if let Some(dtype) = &e.dtype {
27091            self.write_space();
27092            self.write_keyword("SET DATA TYPE");
27093            self.write_space();
27094            self.generate_expression(dtype)?;
27095            if let Some(collate) = &e.collate {
27096                self.write_space();
27097                self.write_keyword("COLLATE");
27098                self.write_space();
27099                self.generate_expression(collate)?;
27100            }
27101            if let Some(using) = &e.using {
27102                self.write_space();
27103                self.write_keyword("USING");
27104                self.write_space();
27105                self.generate_expression(using)?;
27106            }
27107        } else if let Some(default) = &e.default {
27108            self.write_space();
27109            self.write_keyword("SET DEFAULT");
27110            self.write_space();
27111            self.generate_expression(default)?;
27112        } else if let Some(comment) = &e.comment {
27113            self.write_space();
27114            self.write_keyword("COMMENT");
27115            self.write_space();
27116            self.generate_expression(comment)?;
27117        } else if let Some(drop) = &e.drop {
27118            self.write_space();
27119            self.write_keyword("DROP");
27120            self.write_space();
27121            self.generate_expression(drop)?;
27122        } else if let Some(visible) = &e.visible {
27123            self.write_space();
27124            self.generate_expression(visible)?;
27125        } else if let Some(rename_to) = &e.rename_to {
27126            self.write_space();
27127            self.write_keyword("RENAME TO");
27128            self.write_space();
27129            self.generate_expression(rename_to)?;
27130        } else if let Some(allow_null) = &e.allow_null {
27131            self.write_space();
27132            self.generate_expression(allow_null)?;
27133        }
27134        Ok(())
27135    }
27136
27137    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
27138        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
27139        self.write_keyword("ALTER SESSION");
27140        self.write_space();
27141        if e.unset.is_some() {
27142            self.write_keyword("UNSET");
27143        } else {
27144            self.write_keyword("SET");
27145        }
27146        self.write_space();
27147        for (i, expr) in e.expressions.iter().enumerate() {
27148            if i > 0 {
27149                self.write(", ");
27150            }
27151            self.generate_expression(expr)?;
27152        }
27153        Ok(())
27154    }
27155
27156    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
27157        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
27158        self.write_keyword("SET");
27159
27160        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
27161        if let Some(opt) = &e.option {
27162            self.write_space();
27163            self.generate_expression(opt)?;
27164        }
27165
27166        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
27167        // Check if expressions look like property assignments
27168        if !e.expressions.is_empty() {
27169            // Check if this looks like property assignments (for SET PROPERTIES)
27170            let is_properties = e
27171                .expressions
27172                .iter()
27173                .any(|expr| matches!(expr, Expression::Eq(_)));
27174            if is_properties && e.option.is_none() {
27175                self.write_space();
27176                self.write_keyword("PROPERTIES");
27177            }
27178            self.write_space();
27179            for (i, expr) in e.expressions.iter().enumerate() {
27180                if i > 0 {
27181                    self.write(", ");
27182                }
27183                self.generate_expression(expr)?;
27184            }
27185        }
27186
27187        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
27188        if let Some(file_format) = &e.file_format {
27189            self.write(" ");
27190            self.write_keyword("STAGE_FILE_FORMAT");
27191            self.write(" = (");
27192            self.generate_space_separated_properties(file_format)?;
27193            self.write(")");
27194        }
27195
27196        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
27197        if let Some(copy_options) = &e.copy_options {
27198            self.write(" ");
27199            self.write_keyword("STAGE_COPY_OPTIONS");
27200            self.write(" = (");
27201            self.generate_space_separated_properties(copy_options)?;
27202            self.write(")");
27203        }
27204
27205        // Generate TAG ...
27206        if let Some(tag) = &e.tag {
27207            self.write(" ");
27208            self.write_keyword("TAG");
27209            self.write(" ");
27210            self.generate_expression(tag)?;
27211        }
27212
27213        Ok(())
27214    }
27215
27216    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
27217    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
27218        match expr {
27219            Expression::Tuple(t) => {
27220                for (i, prop) in t.expressions.iter().enumerate() {
27221                    if i > 0 {
27222                        self.write(" ");
27223                    }
27224                    self.generate_expression(prop)?;
27225                }
27226            }
27227            _ => {
27228                self.generate_expression(expr)?;
27229            }
27230        }
27231        Ok(())
27232    }
27233
27234    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
27235        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
27236        self.write_keyword("ALTER");
27237        if e.compound.is_some() {
27238            self.write_space();
27239            self.write_keyword("COMPOUND");
27240        }
27241        self.write_space();
27242        self.write_keyword("SORTKEY");
27243        self.write_space();
27244        if let Some(this) = &e.this {
27245            self.generate_expression(this)?;
27246        } else if !e.expressions.is_empty() {
27247            self.write("(");
27248            for (i, expr) in e.expressions.iter().enumerate() {
27249                if i > 0 {
27250                    self.write(", ");
27251                }
27252                self.generate_expression(expr)?;
27253            }
27254            self.write(")");
27255        }
27256        Ok(())
27257    }
27258
27259    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
27260        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
27261        self.write_keyword("ANALYZE");
27262        if !e.options.is_empty() {
27263            self.write_space();
27264            for (i, opt) in e.options.iter().enumerate() {
27265                if i > 0 {
27266                    self.write_space();
27267                }
27268                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
27269                if let Expression::Identifier(id) = opt {
27270                    self.write_keyword(&id.name);
27271                } else {
27272                    self.generate_expression(opt)?;
27273                }
27274            }
27275        }
27276        if let Some(kind) = &e.kind {
27277            self.write_space();
27278            self.write_keyword(kind);
27279        }
27280        if let Some(this) = &e.this {
27281            self.write_space();
27282            self.generate_expression(this)?;
27283        }
27284        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
27285        if !e.columns.is_empty() {
27286            self.write("(");
27287            for (i, col) in e.columns.iter().enumerate() {
27288                if i > 0 {
27289                    self.write(", ");
27290                }
27291                self.write(col);
27292            }
27293            self.write(")");
27294        }
27295        if let Some(partition) = &e.partition {
27296            self.write_space();
27297            self.generate_expression(partition)?;
27298        }
27299        if let Some(mode) = &e.mode {
27300            self.write_space();
27301            self.generate_expression(mode)?;
27302        }
27303        if let Some(expression) = &e.expression {
27304            self.write_space();
27305            self.generate_expression(expression)?;
27306        }
27307        if !e.properties.is_empty() {
27308            self.write_space();
27309            self.write_keyword(self.config.with_properties_prefix);
27310            self.write(" (");
27311            for (i, prop) in e.properties.iter().enumerate() {
27312                if i > 0 {
27313                    self.write(", ");
27314                }
27315                self.generate_expression(prop)?;
27316            }
27317            self.write(")");
27318        }
27319        Ok(())
27320    }
27321
27322    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
27323        // Python: return f"DELETE{kind} STATISTICS"
27324        self.write_keyword("DELETE");
27325        if let Some(kind) = &e.kind {
27326            self.write_space();
27327            self.write_keyword(kind);
27328        }
27329        self.write_space();
27330        self.write_keyword("STATISTICS");
27331        Ok(())
27332    }
27333
27334    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
27335        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
27336        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
27337        if let Expression::Identifier(id) = e.this.as_ref() {
27338            self.write_keyword(&id.name);
27339        } else {
27340            self.generate_expression(&e.this)?;
27341        }
27342        self.write_space();
27343        self.write_keyword("HISTOGRAM ON");
27344        self.write_space();
27345        for (i, expr) in e.expressions.iter().enumerate() {
27346            if i > 0 {
27347                self.write(", ");
27348            }
27349            self.generate_expression(expr)?;
27350        }
27351        if let Some(expression) = &e.expression {
27352            self.write_space();
27353            self.generate_expression(expression)?;
27354        }
27355        if let Some(update_options) = &e.update_options {
27356            self.write_space();
27357            self.generate_expression(update_options)?;
27358            self.write_space();
27359            self.write_keyword("UPDATE");
27360        }
27361        Ok(())
27362    }
27363
27364    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
27365        // Python: return f"LIST CHAINED ROWS{inner_expression}"
27366        self.write_keyword("LIST CHAINED ROWS");
27367        if let Some(expression) = &e.expression {
27368            self.write_space();
27369            self.write_keyword("INTO");
27370            self.write_space();
27371            self.generate_expression(expression)?;
27372        }
27373        Ok(())
27374    }
27375
27376    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
27377        // Python: return f"SAMPLE {sample} {kind}"
27378        self.write_keyword("SAMPLE");
27379        self.write_space();
27380        if let Some(sample) = &e.sample {
27381            self.generate_expression(sample)?;
27382            self.write_space();
27383        }
27384        self.write_keyword(&e.kind);
27385        Ok(())
27386    }
27387
27388    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
27389        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
27390        self.write_keyword(&e.kind);
27391        if let Some(option) = &e.option {
27392            self.write_space();
27393            self.generate_expression(option)?;
27394        }
27395        self.write_space();
27396        self.write_keyword("STATISTICS");
27397        if let Some(this) = &e.this {
27398            self.write_space();
27399            self.generate_expression(this)?;
27400        }
27401        if !e.expressions.is_empty() {
27402            self.write_space();
27403            for (i, expr) in e.expressions.iter().enumerate() {
27404                if i > 0 {
27405                    self.write(", ");
27406                }
27407                self.generate_expression(expr)?;
27408            }
27409        }
27410        Ok(())
27411    }
27412
27413    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
27414        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
27415        self.write_keyword("VALIDATE");
27416        self.write_space();
27417        self.write_keyword(&e.kind);
27418        if let Some(this) = &e.this {
27419            self.write_space();
27420            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
27421            if let Expression::Identifier(id) = this.as_ref() {
27422                self.write_keyword(&id.name);
27423            } else {
27424                self.generate_expression(this)?;
27425            }
27426        }
27427        if let Some(expression) = &e.expression {
27428            self.write_space();
27429            self.write_keyword("INTO");
27430            self.write_space();
27431            self.generate_expression(expression)?;
27432        }
27433        Ok(())
27434    }
27435
27436    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
27437        // Python: return f"WITH {expressions}"
27438        self.write_keyword("WITH");
27439        self.write_space();
27440        for (i, expr) in e.expressions.iter().enumerate() {
27441            if i > 0 {
27442                self.write(", ");
27443            }
27444            self.generate_expression(expr)?;
27445        }
27446        Ok(())
27447    }
27448
27449    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
27450        // Anonymous represents a generic function call: FUNC_NAME(args...)
27451        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
27452        self.generate_expression(&e.this)?;
27453        self.write("(");
27454        for (i, arg) in e.expressions.iter().enumerate() {
27455            if i > 0 {
27456                self.write(", ");
27457            }
27458            self.generate_expression(arg)?;
27459        }
27460        self.write(")");
27461        Ok(())
27462    }
27463
27464    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
27465        // Same as Anonymous but for aggregate functions
27466        self.generate_expression(&e.this)?;
27467        self.write("(");
27468        for (i, arg) in e.expressions.iter().enumerate() {
27469            if i > 0 {
27470                self.write(", ");
27471            }
27472            self.generate_expression(arg)?;
27473        }
27474        self.write(")");
27475        Ok(())
27476    }
27477
27478    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
27479        // Python: return f"{this} APPLY({expr})"
27480        self.generate_expression(&e.this)?;
27481        self.write_space();
27482        self.write_keyword("APPLY");
27483        self.write("(");
27484        self.generate_expression(&e.expression)?;
27485        self.write(")");
27486        Ok(())
27487    }
27488
27489    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
27490        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
27491        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
27492        self.write("(");
27493        self.generate_expression(&e.this)?;
27494        if let Some(percentile) = &e.percentile {
27495            self.write(", ");
27496            self.generate_expression(percentile)?;
27497        }
27498        self.write(")");
27499        Ok(())
27500    }
27501
27502    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
27503        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
27504        self.write_keyword("APPROX_QUANTILE");
27505        self.write("(");
27506        self.generate_expression(&e.this)?;
27507        if let Some(quantile) = &e.quantile {
27508            self.write(", ");
27509            self.generate_expression(quantile)?;
27510        }
27511        if let Some(accuracy) = &e.accuracy {
27512            self.write(", ");
27513            self.generate_expression(accuracy)?;
27514        }
27515        if let Some(weight) = &e.weight {
27516            self.write(", ");
27517            self.generate_expression(weight)?;
27518        }
27519        self.write(")");
27520        Ok(())
27521    }
27522
27523    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
27524        // APPROX_QUANTILES(this, expression)
27525        self.write_keyword("APPROX_QUANTILES");
27526        self.write("(");
27527        self.generate_expression(&e.this)?;
27528        if let Some(expression) = &e.expression {
27529            self.write(", ");
27530            self.generate_expression(expression)?;
27531        }
27532        self.write(")");
27533        Ok(())
27534    }
27535
27536    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
27537        // APPROX_TOP_K(this[, expression][, counters])
27538        self.write_keyword("APPROX_TOP_K");
27539        self.write("(");
27540        self.generate_expression(&e.this)?;
27541        if let Some(expression) = &e.expression {
27542            self.write(", ");
27543            self.generate_expression(expression)?;
27544        }
27545        if let Some(counters) = &e.counters {
27546            self.write(", ");
27547            self.generate_expression(counters)?;
27548        }
27549        self.write(")");
27550        Ok(())
27551    }
27552
27553    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
27554        // APPROX_TOP_K_ACCUMULATE(this[, expression])
27555        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
27556        self.write("(");
27557        self.generate_expression(&e.this)?;
27558        if let Some(expression) = &e.expression {
27559            self.write(", ");
27560            self.generate_expression(expression)?;
27561        }
27562        self.write(")");
27563        Ok(())
27564    }
27565
27566    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
27567        // APPROX_TOP_K_COMBINE(this[, expression])
27568        self.write_keyword("APPROX_TOP_K_COMBINE");
27569        self.write("(");
27570        self.generate_expression(&e.this)?;
27571        if let Some(expression) = &e.expression {
27572            self.write(", ");
27573            self.generate_expression(expression)?;
27574        }
27575        self.write(")");
27576        Ok(())
27577    }
27578
27579    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
27580        // APPROX_TOP_K_ESTIMATE(this[, expression])
27581        self.write_keyword("APPROX_TOP_K_ESTIMATE");
27582        self.write("(");
27583        self.generate_expression(&e.this)?;
27584        if let Some(expression) = &e.expression {
27585            self.write(", ");
27586            self.generate_expression(expression)?;
27587        }
27588        self.write(")");
27589        Ok(())
27590    }
27591
27592    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
27593        // APPROX_TOP_SUM(this, expression[, count])
27594        self.write_keyword("APPROX_TOP_SUM");
27595        self.write("(");
27596        self.generate_expression(&e.this)?;
27597        self.write(", ");
27598        self.generate_expression(&e.expression)?;
27599        if let Some(count) = &e.count {
27600            self.write(", ");
27601            self.generate_expression(count)?;
27602        }
27603        self.write(")");
27604        Ok(())
27605    }
27606
27607    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
27608        // ARG_MAX(this, expression[, count])
27609        self.write_keyword("ARG_MAX");
27610        self.write("(");
27611        self.generate_expression(&e.this)?;
27612        self.write(", ");
27613        self.generate_expression(&e.expression)?;
27614        if let Some(count) = &e.count {
27615            self.write(", ");
27616            self.generate_expression(count)?;
27617        }
27618        self.write(")");
27619        Ok(())
27620    }
27621
27622    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
27623        // ARG_MIN(this, expression[, count])
27624        self.write_keyword("ARG_MIN");
27625        self.write("(");
27626        self.generate_expression(&e.this)?;
27627        self.write(", ");
27628        self.generate_expression(&e.expression)?;
27629        if let Some(count) = &e.count {
27630            self.write(", ");
27631            self.generate_expression(count)?;
27632        }
27633        self.write(")");
27634        Ok(())
27635    }
27636
27637    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
27638        // ARRAY_ALL(this, expression)
27639        self.write_keyword("ARRAY_ALL");
27640        self.write("(");
27641        self.generate_expression(&e.this)?;
27642        self.write(", ");
27643        self.generate_expression(&e.expression)?;
27644        self.write(")");
27645        Ok(())
27646    }
27647
27648    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
27649        // ARRAY_ANY(this, expression) - fallback implementation
27650        self.write_keyword("ARRAY_ANY");
27651        self.write("(");
27652        self.generate_expression(&e.this)?;
27653        self.write(", ");
27654        self.generate_expression(&e.expression)?;
27655        self.write(")");
27656        Ok(())
27657    }
27658
27659    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
27660        // ARRAY_CONSTRUCT_COMPACT(expressions...)
27661        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
27662        self.write("(");
27663        for (i, expr) in e.expressions.iter().enumerate() {
27664            if i > 0 {
27665                self.write(", ");
27666            }
27667            self.generate_expression(expr)?;
27668        }
27669        self.write(")");
27670        Ok(())
27671    }
27672
27673    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
27674        // ARRAY_SUM(this[, expression])
27675        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
27676            self.write("arraySum");
27677        } else {
27678            self.write_keyword("ARRAY_SUM");
27679        }
27680        self.write("(");
27681        self.generate_expression(&e.this)?;
27682        if let Some(expression) = &e.expression {
27683            self.write(", ");
27684            self.generate_expression(expression)?;
27685        }
27686        self.write(")");
27687        Ok(())
27688    }
27689
27690    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
27691        // Python: return f"{this} AT {index}"
27692        self.generate_expression(&e.this)?;
27693        self.write_space();
27694        self.write_keyword("AT");
27695        self.write_space();
27696        self.generate_expression(&e.expression)?;
27697        Ok(())
27698    }
27699
27700    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
27701        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
27702        self.write_keyword("ATTACH");
27703        if e.exists {
27704            self.write_space();
27705            self.write_keyword("IF NOT EXISTS");
27706        }
27707        self.write_space();
27708        self.generate_expression(&e.this)?;
27709        if !e.expressions.is_empty() {
27710            self.write(" (");
27711            for (i, expr) in e.expressions.iter().enumerate() {
27712                if i > 0 {
27713                    self.write(", ");
27714                }
27715                self.generate_expression(expr)?;
27716            }
27717            self.write(")");
27718        }
27719        Ok(())
27720    }
27721
27722    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
27723        // AttachOption: this [expression]
27724        // Python sqlglot: no equals sign, just space-separated
27725        self.generate_expression(&e.this)?;
27726        if let Some(expression) = &e.expression {
27727            self.write_space();
27728            self.generate_expression(expression)?;
27729        }
27730        Ok(())
27731    }
27732
27733    /// Generate the auto_increment keyword and options for a column definition.
27734    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
27735    /// GENERATED AS IDENTITY, etc.
27736    fn generate_auto_increment_keyword(
27737        &mut self,
27738        col: &crate::expressions::ColumnDef,
27739    ) -> Result<()> {
27740        use crate::dialects::DialectType;
27741        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
27742            self.write_keyword("IDENTITY");
27743            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
27744                self.write("(");
27745                if let Some(ref start) = col.auto_increment_start {
27746                    self.generate_expression(start)?;
27747                } else {
27748                    self.write("0");
27749                }
27750                self.write(", ");
27751                if let Some(ref inc) = col.auto_increment_increment {
27752                    self.generate_expression(inc)?;
27753                } else {
27754                    self.write("1");
27755                }
27756                self.write(")");
27757            }
27758        } else if matches!(
27759            self.config.dialect,
27760            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
27761        ) {
27762            self.write_keyword("AUTOINCREMENT");
27763            if let Some(ref start) = col.auto_increment_start {
27764                self.write_space();
27765                self.write_keyword("START");
27766                self.write_space();
27767                self.generate_expression(start)?;
27768            }
27769            if let Some(ref inc) = col.auto_increment_increment {
27770                self.write_space();
27771                self.write_keyword("INCREMENT");
27772                self.write_space();
27773                self.generate_expression(inc)?;
27774            }
27775            if let Some(order) = col.auto_increment_order {
27776                self.write_space();
27777                if order {
27778                    self.write_keyword("ORDER");
27779                } else {
27780                    self.write_keyword("NOORDER");
27781                }
27782            }
27783        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
27784            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
27785            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
27786                self.write(" (");
27787                let mut first = true;
27788                if let Some(ref start) = col.auto_increment_start {
27789                    self.write_keyword("START WITH");
27790                    self.write_space();
27791                    self.generate_expression(start)?;
27792                    first = false;
27793                }
27794                if let Some(ref inc) = col.auto_increment_increment {
27795                    if !first {
27796                        self.write_space();
27797                    }
27798                    self.write_keyword("INCREMENT BY");
27799                    self.write_space();
27800                    self.generate_expression(inc)?;
27801                }
27802                self.write(")");
27803            }
27804        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
27805            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
27806            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
27807            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
27808                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
27809            } else {
27810                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
27811            }
27812            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
27813                self.write(" (");
27814                let mut first = true;
27815                if let Some(ref start) = col.auto_increment_start {
27816                    self.write_keyword("START WITH");
27817                    self.write_space();
27818                    self.generate_expression(start)?;
27819                    first = false;
27820                }
27821                if let Some(ref inc) = col.auto_increment_increment {
27822                    if !first {
27823                        self.write_space();
27824                    }
27825                    self.write_keyword("INCREMENT BY");
27826                    self.write_space();
27827                    self.generate_expression(inc)?;
27828                }
27829                self.write(")");
27830            }
27831        } else if matches!(
27832            self.config.dialect,
27833            Some(DialectType::TSQL) | Some(DialectType::Fabric)
27834        ) {
27835            self.write_keyword("IDENTITY");
27836            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
27837                self.write("(");
27838                if let Some(ref start) = col.auto_increment_start {
27839                    self.generate_expression(start)?;
27840                } else {
27841                    self.write("0");
27842                }
27843                self.write(", ");
27844                if let Some(ref inc) = col.auto_increment_increment {
27845                    self.generate_expression(inc)?;
27846                } else {
27847                    self.write("1");
27848                }
27849                self.write(")");
27850            }
27851        } else {
27852            self.write_keyword("AUTO_INCREMENT");
27853            if let Some(ref start) = col.auto_increment_start {
27854                self.write_space();
27855                self.write_keyword("START");
27856                self.write_space();
27857                self.generate_expression(start)?;
27858            }
27859            if let Some(ref inc) = col.auto_increment_increment {
27860                self.write_space();
27861                self.write_keyword("INCREMENT");
27862                self.write_space();
27863                self.generate_expression(inc)?;
27864            }
27865            if let Some(order) = col.auto_increment_order {
27866                self.write_space();
27867                if order {
27868                    self.write_keyword("ORDER");
27869                } else {
27870                    self.write_keyword("NOORDER");
27871                }
27872            }
27873        }
27874        Ok(())
27875    }
27876
27877    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
27878        // AUTO_INCREMENT=value
27879        self.write_keyword("AUTO_INCREMENT");
27880        self.write("=");
27881        self.generate_expression(&e.this)?;
27882        Ok(())
27883    }
27884
27885    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
27886        // AUTO_REFRESH=value
27887        self.write_keyword("AUTO_REFRESH");
27888        self.write("=");
27889        self.generate_expression(&e.this)?;
27890        Ok(())
27891    }
27892
27893    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
27894        // BACKUP YES|NO (Redshift syntax uses space, not equals)
27895        self.write_keyword("BACKUP");
27896        self.write_space();
27897        self.generate_expression(&e.this)?;
27898        Ok(())
27899    }
27900
27901    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
27902        // BASE64_DECODE_BINARY(this[, alphabet])
27903        self.write_keyword("BASE64_DECODE_BINARY");
27904        self.write("(");
27905        self.generate_expression(&e.this)?;
27906        if let Some(alphabet) = &e.alphabet {
27907            self.write(", ");
27908            self.generate_expression(alphabet)?;
27909        }
27910        self.write(")");
27911        Ok(())
27912    }
27913
27914    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
27915        // BASE64_DECODE_STRING(this[, alphabet])
27916        self.write_keyword("BASE64_DECODE_STRING");
27917        self.write("(");
27918        self.generate_expression(&e.this)?;
27919        if let Some(alphabet) = &e.alphabet {
27920            self.write(", ");
27921            self.generate_expression(alphabet)?;
27922        }
27923        self.write(")");
27924        Ok(())
27925    }
27926
27927    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
27928        // BASE64_ENCODE(this[, max_line_length][, alphabet])
27929        self.write_keyword("BASE64_ENCODE");
27930        self.write("(");
27931        self.generate_expression(&e.this)?;
27932        if let Some(max_line_length) = &e.max_line_length {
27933            self.write(", ");
27934            self.generate_expression(max_line_length)?;
27935        }
27936        if let Some(alphabet) = &e.alphabet {
27937            self.write(", ");
27938            self.generate_expression(alphabet)?;
27939        }
27940        self.write(")");
27941        Ok(())
27942    }
27943
27944    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
27945        // BLOCKCOMPRESSION=... (complex Teradata property)
27946        self.write_keyword("BLOCKCOMPRESSION");
27947        self.write("=");
27948        if let Some(autotemp) = &e.autotemp {
27949            self.write_keyword("AUTOTEMP");
27950            self.write("(");
27951            self.generate_expression(autotemp)?;
27952            self.write(")");
27953        }
27954        if let Some(always) = &e.always {
27955            self.generate_expression(always)?;
27956        }
27957        if let Some(default) = &e.default {
27958            self.generate_expression(default)?;
27959        }
27960        if let Some(manual) = &e.manual {
27961            self.generate_expression(manual)?;
27962        }
27963        if let Some(never) = &e.never {
27964            self.generate_expression(never)?;
27965        }
27966        Ok(())
27967    }
27968
27969    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
27970        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
27971        self.write("((");
27972        self.generate_expression(&e.this)?;
27973        self.write(") ");
27974        self.write_keyword("AND");
27975        self.write(" (");
27976        self.generate_expression(&e.expression)?;
27977        self.write("))");
27978        Ok(())
27979    }
27980
27981    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
27982        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
27983        self.write("((");
27984        self.generate_expression(&e.this)?;
27985        self.write(") ");
27986        self.write_keyword("OR");
27987        self.write(" (");
27988        self.generate_expression(&e.expression)?;
27989        self.write("))");
27990        Ok(())
27991    }
27992
27993    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
27994        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
27995        self.write_keyword("BUILD");
27996        self.write_space();
27997        self.generate_expression(&e.this)?;
27998        Ok(())
27999    }
28000
28001    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
28002        // Byte string literal like B'...' or X'...'
28003        self.generate_expression(&e.this)?;
28004        Ok(())
28005    }
28006
28007    fn generate_case_specific_column_constraint(
28008        &mut self,
28009        e: &CaseSpecificColumnConstraint,
28010    ) -> Result<()> {
28011        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
28012        if e.not_.is_some() {
28013            self.write_keyword("NOT");
28014            self.write_space();
28015        }
28016        self.write_keyword("CASESPECIFIC");
28017        Ok(())
28018    }
28019
28020    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
28021        // Cast to string type (dialect-specific)
28022        self.write_keyword("CAST");
28023        self.write("(");
28024        self.generate_expression(&e.this)?;
28025        if self.config.dialect == Some(DialectType::ClickHouse) {
28026            // ClickHouse: CAST(expr, 'type_string')
28027            self.write(", ");
28028        } else {
28029            self.write_space();
28030            self.write_keyword("AS");
28031            self.write_space();
28032        }
28033        if let Some(to) = &e.to {
28034            self.generate_expression(to)?;
28035        }
28036        self.write(")");
28037        Ok(())
28038    }
28039
28040    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
28041        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
28042        // Python: f"CHANGES ({information}){at_before}{end}"
28043        self.write_keyword("CHANGES");
28044        self.write(" (");
28045        if let Some(information) = &e.information {
28046            self.write_keyword("INFORMATION");
28047            self.write(" => ");
28048            self.generate_expression(information)?;
28049        }
28050        self.write(")");
28051        // at_before and end are HistoricalData expressions that generate their own keywords
28052        if let Some(at_before) = &e.at_before {
28053            self.write(" ");
28054            self.generate_expression(at_before)?;
28055        }
28056        if let Some(end) = &e.end {
28057            self.write(" ");
28058            self.generate_expression(end)?;
28059        }
28060        Ok(())
28061    }
28062
28063    fn generate_character_set_column_constraint(
28064        &mut self,
28065        e: &CharacterSetColumnConstraint,
28066    ) -> Result<()> {
28067        // CHARACTER SET charset_name
28068        self.write_keyword("CHARACTER SET");
28069        self.write_space();
28070        self.generate_expression(&e.this)?;
28071        Ok(())
28072    }
28073
28074    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
28075        // [DEFAULT] CHARACTER SET=value
28076        if e.default.is_some() {
28077            self.write_keyword("DEFAULT");
28078            self.write_space();
28079        }
28080        self.write_keyword("CHARACTER SET");
28081        self.write("=");
28082        self.generate_expression(&e.this)?;
28083        Ok(())
28084    }
28085
28086    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
28087        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
28088        self.write_keyword("CHECK");
28089        self.write(" (");
28090        self.generate_expression(&e.this)?;
28091        self.write(")");
28092        if e.enforced.is_some() {
28093            self.write_space();
28094            self.write_keyword("ENFORCED");
28095        }
28096        Ok(())
28097    }
28098
28099    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
28100        // Python: return f"ASSUME ({self.sql(e, 'this')})"
28101        self.write_keyword("ASSUME");
28102        self.write(" (");
28103        self.generate_expression(&e.this)?;
28104        self.write(")");
28105        Ok(())
28106    }
28107
28108    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
28109        // CHECK_JSON(this)
28110        self.write_keyword("CHECK_JSON");
28111        self.write("(");
28112        self.generate_expression(&e.this)?;
28113        self.write(")");
28114        Ok(())
28115    }
28116
28117    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
28118        // CHECK_XML(this)
28119        self.write_keyword("CHECK_XML");
28120        self.write("(");
28121        self.generate_expression(&e.this)?;
28122        self.write(")");
28123        Ok(())
28124    }
28125
28126    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
28127        // CHECKSUM=[ON|OFF|DEFAULT]
28128        self.write_keyword("CHECKSUM");
28129        self.write("=");
28130        if e.on.is_some() {
28131            self.write_keyword("ON");
28132        } else if e.default.is_some() {
28133            self.write_keyword("DEFAULT");
28134        } else {
28135            self.write_keyword("OFF");
28136        }
28137        Ok(())
28138    }
28139
28140    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
28141        // Python: return f"{shallow}{keyword} {this}"
28142        if e.shallow.is_some() {
28143            self.write_keyword("SHALLOW");
28144            self.write_space();
28145        }
28146        if e.copy.is_some() {
28147            self.write_keyword("COPY");
28148        } else {
28149            self.write_keyword("CLONE");
28150        }
28151        self.write_space();
28152        self.generate_expression(&e.this)?;
28153        Ok(())
28154    }
28155
28156    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
28157        // CLUSTER BY (expressions)
28158        self.write_keyword("CLUSTER BY");
28159        self.write(" (");
28160        for (i, ord) in e.expressions.iter().enumerate() {
28161            if i > 0 {
28162                self.write(", ");
28163            }
28164            self.generate_ordered(ord)?;
28165        }
28166        self.write(")");
28167        Ok(())
28168    }
28169
28170    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
28171        // BigQuery table property: CLUSTER BY col1, col2
28172        self.write_keyword("CLUSTER BY");
28173        self.write_space();
28174        for (i, col) in e.columns.iter().enumerate() {
28175            if i > 0 {
28176                self.write(", ");
28177            }
28178            self.generate_identifier(col)?;
28179        }
28180        Ok(())
28181    }
28182
28183    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
28184        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
28185        self.write_keyword("CLUSTERED BY");
28186        self.write(" (");
28187        for (i, expr) in e.expressions.iter().enumerate() {
28188            if i > 0 {
28189                self.write(", ");
28190            }
28191            self.generate_expression(expr)?;
28192        }
28193        self.write(")");
28194        if let Some(sorted_by) = &e.sorted_by {
28195            self.write_space();
28196            self.write_keyword("SORTED BY");
28197            self.write(" (");
28198            // Unwrap Tuple to avoid double parentheses
28199            if let Expression::Tuple(t) = sorted_by.as_ref() {
28200                for (i, expr) in t.expressions.iter().enumerate() {
28201                    if i > 0 {
28202                        self.write(", ");
28203                    }
28204                    self.generate_expression(expr)?;
28205                }
28206            } else {
28207                self.generate_expression(sorted_by)?;
28208            }
28209            self.write(")");
28210        }
28211        if let Some(buckets) = &e.buckets {
28212            self.write_space();
28213            self.write_keyword("INTO");
28214            self.write_space();
28215            self.generate_expression(buckets)?;
28216            self.write_space();
28217            self.write_keyword("BUCKETS");
28218        }
28219        Ok(())
28220    }
28221
28222    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
28223        // [DEFAULT] COLLATE [=] value
28224        // BigQuery uses space: DEFAULT COLLATE 'en'
28225        // Others use equals: COLLATE='en'
28226        if e.default.is_some() {
28227            self.write_keyword("DEFAULT");
28228            self.write_space();
28229        }
28230        self.write_keyword("COLLATE");
28231        // BigQuery uses space between COLLATE and value
28232        match self.config.dialect {
28233            Some(DialectType::BigQuery) => self.write_space(),
28234            _ => self.write("="),
28235        }
28236        self.generate_expression(&e.this)?;
28237        Ok(())
28238    }
28239
28240    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
28241        // ColumnConstraint is an enum
28242        match e {
28243            ColumnConstraint::NotNull => {
28244                self.write_keyword("NOT NULL");
28245            }
28246            ColumnConstraint::Null => {
28247                self.write_keyword("NULL");
28248            }
28249            ColumnConstraint::Unique => {
28250                self.write_keyword("UNIQUE");
28251            }
28252            ColumnConstraint::PrimaryKey => {
28253                self.write_keyword("PRIMARY KEY");
28254            }
28255            ColumnConstraint::Default(expr) => {
28256                self.write_keyword("DEFAULT");
28257                self.write_space();
28258                self.generate_expression(expr)?;
28259            }
28260            ColumnConstraint::Check(expr) => {
28261                self.write_keyword("CHECK");
28262                self.write(" (");
28263                self.generate_expression(expr)?;
28264                self.write(")");
28265            }
28266            ColumnConstraint::References(fk_ref) => {
28267                if fk_ref.has_foreign_key_keywords {
28268                    self.write_keyword("FOREIGN KEY");
28269                    self.write_space();
28270                }
28271                self.write_keyword("REFERENCES");
28272                self.write_space();
28273                self.generate_table(&fk_ref.table)?;
28274                if !fk_ref.columns.is_empty() {
28275                    self.write(" (");
28276                    for (i, col) in fk_ref.columns.iter().enumerate() {
28277                        if i > 0 {
28278                            self.write(", ");
28279                        }
28280                        self.generate_identifier(col)?;
28281                    }
28282                    self.write(")");
28283                }
28284            }
28285            ColumnConstraint::GeneratedAsIdentity(gen) => {
28286                self.write_keyword("GENERATED");
28287                self.write_space();
28288                if gen.always {
28289                    self.write_keyword("ALWAYS");
28290                } else {
28291                    self.write_keyword("BY DEFAULT");
28292                    if gen.on_null {
28293                        self.write_space();
28294                        self.write_keyword("ON NULL");
28295                    }
28296                }
28297                self.write_space();
28298                self.write_keyword("AS IDENTITY");
28299            }
28300            ColumnConstraint::Collate(collation) => {
28301                self.write_keyword("COLLATE");
28302                self.write_space();
28303                self.generate_identifier(collation)?;
28304            }
28305            ColumnConstraint::Comment(comment) => {
28306                self.write_keyword("COMMENT");
28307                self.write(" '");
28308                self.write(comment);
28309                self.write("'");
28310            }
28311            ColumnConstraint::ComputedColumn(cc) => {
28312                self.generate_computed_column_inline(cc)?;
28313            }
28314            ColumnConstraint::GeneratedAsRow(gar) => {
28315                self.generate_generated_as_row_inline(gar)?;
28316            }
28317            ColumnConstraint::Tags(tags) => {
28318                self.write_keyword("TAG");
28319                self.write(" (");
28320                for (i, expr) in tags.expressions.iter().enumerate() {
28321                    if i > 0 {
28322                        self.write(", ");
28323                    }
28324                    self.generate_expression(expr)?;
28325                }
28326                self.write(")");
28327            }
28328            ColumnConstraint::Path(path_expr) => {
28329                self.write_keyword("PATH");
28330                self.write_space();
28331                self.generate_expression(path_expr)?;
28332            }
28333        }
28334        Ok(())
28335    }
28336
28337    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
28338        // ColumnPosition is an enum
28339        match e {
28340            ColumnPosition::First => {
28341                self.write_keyword("FIRST");
28342            }
28343            ColumnPosition::After(ident) => {
28344                self.write_keyword("AFTER");
28345                self.write_space();
28346                self.generate_identifier(ident)?;
28347            }
28348        }
28349        Ok(())
28350    }
28351
28352    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
28353        // column(prefix)
28354        self.generate_expression(&e.this)?;
28355        self.write("(");
28356        self.generate_expression(&e.expression)?;
28357        self.write(")");
28358        Ok(())
28359    }
28360
28361    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
28362        // If unpack is true, this came from * COLUMNS(pattern)
28363        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
28364        if let Some(ref unpack) = e.unpack {
28365            if let Expression::Boolean(b) = unpack.as_ref() {
28366                if b.value {
28367                    self.write("*");
28368                }
28369            }
28370        }
28371        self.write_keyword("COLUMNS");
28372        self.write("(");
28373        self.generate_expression(&e.this)?;
28374        self.write(")");
28375        Ok(())
28376    }
28377
28378    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
28379        // Combined aggregate: FUNC(args) combined
28380        self.generate_expression(&e.this)?;
28381        self.write("(");
28382        for (i, expr) in e.expressions.iter().enumerate() {
28383            if i > 0 {
28384                self.write(", ");
28385            }
28386            self.generate_expression(expr)?;
28387        }
28388        self.write(")");
28389        Ok(())
28390    }
28391
28392    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
28393        // Combined parameterized aggregate: FUNC(params)(expressions)
28394        self.generate_expression(&e.this)?;
28395        self.write("(");
28396        for (i, param) in e.params.iter().enumerate() {
28397            if i > 0 {
28398                self.write(", ");
28399            }
28400            self.generate_expression(param)?;
28401        }
28402        self.write(")(");
28403        for (i, expr) in e.expressions.iter().enumerate() {
28404            if i > 0 {
28405                self.write(", ");
28406            }
28407            self.generate_expression(expr)?;
28408        }
28409        self.write(")");
28410        Ok(())
28411    }
28412
28413    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
28414        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
28415        self.write_keyword("COMMIT");
28416
28417        // TSQL always uses COMMIT TRANSACTION
28418        if e.this.is_none()
28419            && matches!(
28420                self.config.dialect,
28421                Some(DialectType::TSQL) | Some(DialectType::Fabric)
28422            )
28423        {
28424            self.write_space();
28425            self.write_keyword("TRANSACTION");
28426        }
28427
28428        // Check if this has TRANSACTION keyword or transaction name
28429        if let Some(this) = &e.this {
28430            // Check if it's just the "TRANSACTION" marker or an actual transaction name
28431            let is_transaction_marker = matches!(
28432                this.as_ref(),
28433                Expression::Identifier(id) if id.name == "TRANSACTION"
28434            );
28435
28436            self.write_space();
28437            self.write_keyword("TRANSACTION");
28438
28439            // If it's a real transaction name, output it
28440            if !is_transaction_marker {
28441                self.write_space();
28442                self.generate_expression(this)?;
28443            }
28444        }
28445
28446        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
28447        if let Some(durability) = &e.durability {
28448            self.write_space();
28449            self.write_keyword("WITH");
28450            self.write(" (");
28451            self.write_keyword("DELAYED_DURABILITY");
28452            self.write(" = ");
28453            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
28454                self.write_keyword("ON");
28455            } else {
28456                self.write_keyword("OFF");
28457            }
28458            self.write(")");
28459        }
28460
28461        // Output AND [NO] CHAIN
28462        if let Some(chain) = &e.chain {
28463            self.write_space();
28464            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
28465                self.write_keyword("AND NO CHAIN");
28466            } else {
28467                self.write_keyword("AND CHAIN");
28468            }
28469        }
28470        Ok(())
28471    }
28472
28473    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
28474        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
28475        self.write("[");
28476        self.generate_expression(&e.this)?;
28477        self.write_space();
28478        self.write_keyword("FOR");
28479        self.write_space();
28480        self.generate_expression(&e.expression)?;
28481        // Handle optional position variable (for enumerate-like syntax)
28482        if let Some(pos) = &e.position {
28483            self.write(", ");
28484            self.generate_expression(pos)?;
28485        }
28486        if let Some(iterator) = &e.iterator {
28487            self.write_space();
28488            self.write_keyword("IN");
28489            self.write_space();
28490            self.generate_expression(iterator)?;
28491        }
28492        if let Some(condition) = &e.condition {
28493            self.write_space();
28494            self.write_keyword("IF");
28495            self.write_space();
28496            self.generate_expression(condition)?;
28497        }
28498        self.write("]");
28499        Ok(())
28500    }
28501
28502    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
28503        // COMPRESS(this[, method])
28504        self.write_keyword("COMPRESS");
28505        self.write("(");
28506        self.generate_expression(&e.this)?;
28507        if let Some(method) = &e.method {
28508            self.write(", '");
28509            self.write(method);
28510            self.write("'");
28511        }
28512        self.write(")");
28513        Ok(())
28514    }
28515
28516    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
28517        // Python: return f"COMPRESS {this}"
28518        self.write_keyword("COMPRESS");
28519        if let Some(this) = &e.this {
28520            self.write_space();
28521            self.generate_expression(this)?;
28522        }
28523        Ok(())
28524    }
28525
28526    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
28527        // Python: return f"AS {this}{persisted}"
28528        self.write_keyword("AS");
28529        self.write_space();
28530        self.generate_expression(&e.this)?;
28531        if e.not_null.is_some() {
28532            self.write_space();
28533            self.write_keyword("PERSISTED NOT NULL");
28534        } else if e.persisted.is_some() {
28535            self.write_space();
28536            self.write_keyword("PERSISTED");
28537        }
28538        Ok(())
28539    }
28540
28541    /// Generate a ComputedColumn constraint inline within a column definition.
28542    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
28543    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
28544    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
28545        let computed_expr = if matches!(
28546            self.config.dialect,
28547            Some(DialectType::TSQL) | Some(DialectType::Fabric)
28548        ) {
28549            match &*cc.expression {
28550                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
28551                {
28552                    let wrapped = Expression::Cast(Box::new(Cast {
28553                        this: y.this.clone(),
28554                        to: DataType::Date,
28555                        trailing_comments: Vec::new(),
28556                        double_colon_syntax: false,
28557                        format: None,
28558                        default: None,
28559                        inferred_type: None,
28560                    }));
28561                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
28562                }
28563                Expression::Function(f)
28564                    if f.name.eq_ignore_ascii_case("YEAR")
28565                        && f.args.len() == 1
28566                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
28567                {
28568                    let wrapped = Expression::Cast(Box::new(Cast {
28569                        this: f.args[0].clone(),
28570                        to: DataType::Date,
28571                        trailing_comments: Vec::new(),
28572                        double_colon_syntax: false,
28573                        format: None,
28574                        default: None,
28575                        inferred_type: None,
28576                    }));
28577                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
28578                }
28579                _ => *cc.expression.clone(),
28580            }
28581        } else {
28582            *cc.expression.clone()
28583        };
28584
28585        match cc.persistence_kind.as_deref() {
28586            Some("STORED") | Some("VIRTUAL") => {
28587                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
28588                self.write_keyword("GENERATED ALWAYS AS");
28589                self.write(" (");
28590                self.generate_expression(&computed_expr)?;
28591                self.write(")");
28592                self.write_space();
28593                if cc.persisted {
28594                    self.write_keyword("STORED");
28595                } else {
28596                    self.write_keyword("VIRTUAL");
28597                }
28598            }
28599            Some("PERSISTED") => {
28600                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
28601                self.write_keyword("AS");
28602                self.write(" (");
28603                self.generate_expression(&computed_expr)?;
28604                self.write(")");
28605                self.write_space();
28606                self.write_keyword("PERSISTED");
28607                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
28608                if let Some(ref dt) = cc.data_type {
28609                    self.write_space();
28610                    self.generate_data_type(dt)?;
28611                }
28612                if cc.not_null {
28613                    self.write_space();
28614                    self.write_keyword("NOT NULL");
28615                }
28616            }
28617            _ => {
28618                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
28619                // TSQL computed column without PERSISTED: AS (expr)
28620                if matches!(
28621                    self.config.dialect,
28622                    Some(DialectType::Spark)
28623                        | Some(DialectType::Databricks)
28624                        | Some(DialectType::Hive)
28625                ) {
28626                    self.write_keyword("GENERATED ALWAYS AS");
28627                    self.write(" (");
28628                    self.generate_expression(&computed_expr)?;
28629                    self.write(")");
28630                } else if matches!(
28631                    self.config.dialect,
28632                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
28633                ) {
28634                    self.write_keyword("AS");
28635                    let omit_parens = matches!(computed_expr, Expression::Year(_))
28636                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
28637                    if omit_parens {
28638                        self.write_space();
28639                        self.generate_expression(&computed_expr)?;
28640                    } else {
28641                        self.write(" (");
28642                        self.generate_expression(&computed_expr)?;
28643                        self.write(")");
28644                    }
28645                } else {
28646                    self.write_keyword("AS");
28647                    self.write(" (");
28648                    self.generate_expression(&computed_expr)?;
28649                    self.write(")");
28650                }
28651            }
28652        }
28653        Ok(())
28654    }
28655
28656    /// Generate a GeneratedAsRow constraint inline within a column definition.
28657    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
28658    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
28659        self.write_keyword("GENERATED ALWAYS AS ROW ");
28660        if gar.start {
28661            self.write_keyword("START");
28662        } else {
28663            self.write_keyword("END");
28664        }
28665        if gar.hidden {
28666            self.write_space();
28667            self.write_keyword("HIDDEN");
28668        }
28669        Ok(())
28670    }
28671
28672    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
28673    fn generate_system_versioning_content(
28674        &mut self,
28675        e: &WithSystemVersioningProperty,
28676    ) -> Result<()> {
28677        let mut parts = Vec::new();
28678
28679        if let Some(this) = &e.this {
28680            let mut s = String::from("HISTORY_TABLE=");
28681            let mut gen = Generator::with_arc_config(self.config.clone());
28682            gen.generate_expression(this)?;
28683            s.push_str(&gen.output);
28684            parts.push(s);
28685        }
28686
28687        if let Some(data_consistency) = &e.data_consistency {
28688            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
28689            let mut gen = Generator::with_arc_config(self.config.clone());
28690            gen.generate_expression(data_consistency)?;
28691            s.push_str(&gen.output);
28692            parts.push(s);
28693        }
28694
28695        if let Some(retention_period) = &e.retention_period {
28696            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
28697            let mut gen = Generator::with_arc_config(self.config.clone());
28698            gen.generate_expression(retention_period)?;
28699            s.push_str(&gen.output);
28700            parts.push(s);
28701        }
28702
28703        self.write_keyword("SYSTEM_VERSIONING");
28704        self.write("=");
28705
28706        if !parts.is_empty() {
28707            self.write_keyword("ON");
28708            self.write("(");
28709            self.write(&parts.join(", "));
28710            self.write(")");
28711        } else if e.on.is_some() {
28712            self.write_keyword("ON");
28713        } else {
28714            self.write_keyword("OFF");
28715        }
28716
28717        Ok(())
28718    }
28719
28720    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
28721        // Conditional INSERT for multi-table inserts
28722        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
28723        if e.else_.is_some() {
28724            self.write_keyword("ELSE");
28725            self.write_space();
28726        } else if let Some(expression) = &e.expression {
28727            self.write_keyword("WHEN");
28728            self.write_space();
28729            self.generate_expression(expression)?;
28730            self.write_space();
28731            self.write_keyword("THEN");
28732            self.write_space();
28733        }
28734
28735        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
28736        // without the "INSERT " prefix
28737        if let Expression::Insert(insert) = e.this.as_ref() {
28738            self.write_keyword("INTO");
28739            self.write_space();
28740            self.generate_table(&insert.table)?;
28741
28742            // Optional column list
28743            if !insert.columns.is_empty() {
28744                self.write(" (");
28745                for (i, col) in insert.columns.iter().enumerate() {
28746                    if i > 0 {
28747                        self.write(", ");
28748                    }
28749                    self.generate_identifier(col)?;
28750                }
28751                self.write(")");
28752            }
28753
28754            // Optional VALUES clause
28755            if !insert.values.is_empty() {
28756                self.write_space();
28757                self.write_keyword("VALUES");
28758                for (row_idx, row) in insert.values.iter().enumerate() {
28759                    if row_idx > 0 {
28760                        self.write(", ");
28761                    }
28762                    self.write(" (");
28763                    for (i, val) in row.iter().enumerate() {
28764                        if i > 0 {
28765                            self.write(", ");
28766                        }
28767                        self.generate_expression(val)?;
28768                    }
28769                    self.write(")");
28770                }
28771            }
28772        } else {
28773            // Fallback for non-Insert expressions
28774            self.generate_expression(&e.this)?;
28775        }
28776        Ok(())
28777    }
28778
28779    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
28780        // Python: return f"CONSTRAINT {this} {expressions}"
28781        self.write_keyword("CONSTRAINT");
28782        self.write_space();
28783        self.generate_expression(&e.this)?;
28784        if !e.expressions.is_empty() {
28785            self.write_space();
28786            for (i, expr) in e.expressions.iter().enumerate() {
28787                if i > 0 {
28788                    self.write_space();
28789                }
28790                self.generate_expression(expr)?;
28791            }
28792        }
28793        Ok(())
28794    }
28795
28796    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
28797        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
28798        self.write_keyword("CONVERT_TIMEZONE");
28799        self.write("(");
28800        let mut first = true;
28801        if let Some(source_tz) = &e.source_tz {
28802            self.generate_expression(source_tz)?;
28803            first = false;
28804        }
28805        if let Some(target_tz) = &e.target_tz {
28806            if !first {
28807                self.write(", ");
28808            }
28809            self.generate_expression(target_tz)?;
28810            first = false;
28811        }
28812        if let Some(timestamp) = &e.timestamp {
28813            if !first {
28814                self.write(", ");
28815            }
28816            self.generate_expression(timestamp)?;
28817        }
28818        self.write(")");
28819        Ok(())
28820    }
28821
28822    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
28823        // CONVERT(this USING dest)
28824        self.write_keyword("CONVERT");
28825        self.write("(");
28826        self.generate_expression(&e.this)?;
28827        if let Some(dest) = &e.dest {
28828            self.write_space();
28829            self.write_keyword("USING");
28830            self.write_space();
28831            self.generate_expression(dest)?;
28832        }
28833        self.write(")");
28834        Ok(())
28835    }
28836
28837    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
28838        self.write_keyword("COPY");
28839        if e.is_into {
28840            self.write_space();
28841            self.write_keyword("INTO");
28842        }
28843        self.write_space();
28844
28845        // Generate target table or query (or stage for COPY INTO @stage)
28846        if let Expression::Literal(lit) = &e.this {
28847            if let Literal::String(s) = lit.as_ref() {
28848                if s.starts_with('@') {
28849                    self.write(s);
28850                } else {
28851                    self.generate_expression(&e.this)?;
28852                }
28853            }
28854        } else {
28855            self.generate_expression(&e.this)?;
28856        }
28857
28858        // FROM or TO based on kind
28859        if e.kind {
28860            // kind=true means FROM (loading into table)
28861            if self.config.pretty {
28862                self.write_newline();
28863            } else {
28864                self.write_space();
28865            }
28866            self.write_keyword("FROM");
28867            self.write_space();
28868        } else if !e.files.is_empty() {
28869            // kind=false means TO (exporting)
28870            if self.config.pretty {
28871                self.write_newline();
28872            } else {
28873                self.write_space();
28874            }
28875            self.write_keyword("TO");
28876            self.write_space();
28877        }
28878
28879        // Generate source/destination files
28880        for (i, file) in e.files.iter().enumerate() {
28881            if i > 0 {
28882                self.write_space();
28883            }
28884            // For stage references (strings starting with @), output without quotes
28885            if let Expression::Literal(lit) = file {
28886                if let Literal::String(s) = lit.as_ref() {
28887                    if s.starts_with('@') {
28888                        self.write(s);
28889                    } else {
28890                        self.generate_expression(file)?;
28891                    }
28892                }
28893            } else if let Expression::Identifier(id) = file {
28894                // Backtick-quoted file path (Databricks style: `s3://link`)
28895                if id.quoted {
28896                    self.write("`");
28897                    self.write(&id.name);
28898                    self.write("`");
28899                } else {
28900                    self.generate_expression(file)?;
28901                }
28902            } else {
28903                self.generate_expression(file)?;
28904            }
28905        }
28906
28907        // Generate credentials if present (Snowflake style - not wrapped in WITH)
28908        if !e.with_wrapped {
28909            if let Some(ref creds) = e.credentials {
28910                if let Some(ref storage) = creds.storage {
28911                    if self.config.pretty {
28912                        self.write_newline();
28913                    } else {
28914                        self.write_space();
28915                    }
28916                    self.write_keyword("STORAGE_INTEGRATION");
28917                    self.write(" = ");
28918                    self.write(storage);
28919                }
28920                if creds.credentials.is_empty() {
28921                    // Empty credentials: CREDENTIALS = ()
28922                    if self.config.pretty {
28923                        self.write_newline();
28924                    } else {
28925                        self.write_space();
28926                    }
28927                    self.write_keyword("CREDENTIALS");
28928                    self.write(" = ()");
28929                } else {
28930                    if self.config.pretty {
28931                        self.write_newline();
28932                    } else {
28933                        self.write_space();
28934                    }
28935                    self.write_keyword("CREDENTIALS");
28936                    // Check if this is Redshift-style (single value with empty key)
28937                    // vs Snowflake-style (multiple key=value pairs)
28938                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
28939                        // Redshift style: CREDENTIALS 'value'
28940                        self.write(" '");
28941                        self.write(&creds.credentials[0].1);
28942                        self.write("'");
28943                    } else {
28944                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
28945                        self.write(" = (");
28946                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
28947                            if i > 0 {
28948                                self.write_space();
28949                            }
28950                            self.write(k);
28951                            self.write("='");
28952                            self.write(v);
28953                            self.write("'");
28954                        }
28955                        self.write(")");
28956                    }
28957                }
28958                if let Some(ref encryption) = creds.encryption {
28959                    self.write_space();
28960                    self.write_keyword("ENCRYPTION");
28961                    self.write(" = ");
28962                    self.write(encryption);
28963                }
28964            }
28965        }
28966
28967        // Generate parameters
28968        if !e.params.is_empty() {
28969            if e.with_wrapped {
28970                // DuckDB/PostgreSQL/TSQL WITH (...) format
28971                self.write_space();
28972                self.write_keyword("WITH");
28973                self.write(" (");
28974                for (i, param) in e.params.iter().enumerate() {
28975                    if i > 0 {
28976                        self.write(", ");
28977                    }
28978                    self.generate_copy_param_with_format(param)?;
28979                }
28980                self.write(")");
28981            } else {
28982                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
28983                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
28984                // For Snowflake: KEY = VALUE
28985                for param in &e.params {
28986                    if self.config.pretty {
28987                        self.write_newline();
28988                    } else {
28989                        self.write_space();
28990                    }
28991                    // Preserve original case of parameter name (important for Redshift COPY options)
28992                    self.write(&param.name);
28993                    if let Some(ref value) = param.value {
28994                        // Use = only if it was present in the original (param.eq)
28995                        if param.eq {
28996                            self.write(" = ");
28997                        } else {
28998                            self.write(" ");
28999                        }
29000                        if !param.values.is_empty() {
29001                            self.write("(");
29002                            for (i, v) in param.values.iter().enumerate() {
29003                                if i > 0 {
29004                                    self.write_space();
29005                                }
29006                                self.generate_copy_nested_param(v)?;
29007                            }
29008                            self.write(")");
29009                        } else {
29010                            // For COPY parameter values, output identifiers without quoting
29011                            self.generate_copy_param_value(value)?;
29012                        }
29013                    } else if !param.values.is_empty() {
29014                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
29015                        if param.eq {
29016                            self.write(" = (");
29017                        } else {
29018                            self.write(" (");
29019                        }
29020                        // Determine separator for values inside parentheses:
29021                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
29022                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
29023                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
29024                        let is_key_value_pairs = param
29025                            .values
29026                            .first()
29027                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
29028                        let sep = if is_key_value_pairs && param.eq {
29029                            " "
29030                        } else {
29031                            ", "
29032                        };
29033                        for (i, v) in param.values.iter().enumerate() {
29034                            if i > 0 {
29035                                self.write(sep);
29036                            }
29037                            self.generate_copy_nested_param(v)?;
29038                        }
29039                        self.write(")");
29040                    }
29041                }
29042            }
29043        }
29044
29045        Ok(())
29046    }
29047
29048    /// Generate a COPY parameter in WITH (...) format
29049    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
29050    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
29051        self.write_keyword(&param.name);
29052        if !param.values.is_empty() {
29053            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
29054            self.write(" = (");
29055            for (i, v) in param.values.iter().enumerate() {
29056                if i > 0 {
29057                    self.write(", ");
29058                }
29059                self.generate_copy_nested_param(v)?;
29060            }
29061            self.write(")");
29062        } else if let Some(ref value) = param.value {
29063            if param.eq {
29064                self.write(" = ");
29065            } else {
29066                self.write(" ");
29067            }
29068            self.generate_expression(value)?;
29069        }
29070        Ok(())
29071    }
29072
29073    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
29074    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
29075        match expr {
29076            Expression::Eq(eq) => {
29077                // Generate key
29078                match &eq.left {
29079                    Expression::Column(c) => self.write(&c.name.name),
29080                    _ => self.generate_expression(&eq.left)?,
29081                }
29082                self.write("=");
29083                // Generate value
29084                match &eq.right {
29085                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
29086                        let Literal::String(s) = lit.as_ref() else {
29087                            unreachable!()
29088                        };
29089                        self.write("'");
29090                        self.write(s);
29091                        self.write("'");
29092                    }
29093                    Expression::Tuple(t) => {
29094                        // For lists like NULL_IF=('', 'str1')
29095                        self.write("(");
29096                        if self.config.pretty {
29097                            self.write_newline();
29098                            self.indent_level += 1;
29099                            for (i, item) in t.expressions.iter().enumerate() {
29100                                if i > 0 {
29101                                    self.write(", ");
29102                                }
29103                                self.write_indent();
29104                                self.generate_expression(item)?;
29105                            }
29106                            self.write_newline();
29107                            self.indent_level -= 1;
29108                        } else {
29109                            for (i, item) in t.expressions.iter().enumerate() {
29110                                if i > 0 {
29111                                    self.write(", ");
29112                                }
29113                                self.generate_expression(item)?;
29114                            }
29115                        }
29116                        self.write(")");
29117                    }
29118                    _ => self.generate_expression(&eq.right)?,
29119                }
29120                Ok(())
29121            }
29122            Expression::Column(c) => {
29123                // Standalone keyword like COMPRESSION
29124                self.write(&c.name.name);
29125                Ok(())
29126            }
29127            _ => self.generate_expression(expr),
29128        }
29129    }
29130
29131    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
29132    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
29133    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
29134        match expr {
29135            Expression::Column(c) => {
29136                // Output identifier, preserving quotes if originally quoted
29137                if c.name.quoted {
29138                    self.write("\"");
29139                    self.write(&c.name.name);
29140                    self.write("\"");
29141                } else {
29142                    self.write(&c.name.name);
29143                }
29144                Ok(())
29145            }
29146            Expression::Identifier(id) => {
29147                // Output identifier, preserving quotes if originally quoted
29148                if id.quoted {
29149                    self.write("\"");
29150                    self.write(&id.name);
29151                    self.write("\"");
29152                } else {
29153                    self.write(&id.name);
29154                }
29155                Ok(())
29156            }
29157            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
29158                let Literal::String(s) = lit.as_ref() else {
29159                    unreachable!()
29160                };
29161                // Output string with quotes
29162                self.write("'");
29163                self.write(s);
29164                self.write("'");
29165                Ok(())
29166            }
29167            _ => self.generate_expression(expr),
29168        }
29169    }
29170
29171    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
29172        self.write_keyword(&e.name);
29173        if let Some(ref value) = e.value {
29174            if e.eq {
29175                self.write(" = ");
29176            } else {
29177                self.write(" ");
29178            }
29179            self.generate_expression(value)?;
29180        }
29181        if !e.values.is_empty() {
29182            if e.eq {
29183                self.write(" = ");
29184            } else {
29185                self.write(" ");
29186            }
29187            self.write("(");
29188            for (i, v) in e.values.iter().enumerate() {
29189                if i > 0 {
29190                    self.write(", ");
29191                }
29192                self.generate_expression(v)?;
29193            }
29194            self.write(")");
29195        }
29196        Ok(())
29197    }
29198
29199    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
29200        // CORR(this, expression)
29201        self.write_keyword("CORR");
29202        self.write("(");
29203        self.generate_expression(&e.this)?;
29204        self.write(", ");
29205        self.generate_expression(&e.expression)?;
29206        self.write(")");
29207        Ok(())
29208    }
29209
29210    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
29211        // COSINE_DISTANCE(this, expression)
29212        self.write_keyword("COSINE_DISTANCE");
29213        self.write("(");
29214        self.generate_expression(&e.this)?;
29215        self.write(", ");
29216        self.generate_expression(&e.expression)?;
29217        self.write(")");
29218        Ok(())
29219    }
29220
29221    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
29222        // COVAR_POP(this, expression)
29223        self.write_keyword("COVAR_POP");
29224        self.write("(");
29225        self.generate_expression(&e.this)?;
29226        self.write(", ");
29227        self.generate_expression(&e.expression)?;
29228        self.write(")");
29229        Ok(())
29230    }
29231
29232    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
29233        // COVAR_SAMP(this, expression)
29234        self.write_keyword("COVAR_SAMP");
29235        self.write("(");
29236        self.generate_expression(&e.this)?;
29237        self.write(", ");
29238        self.generate_expression(&e.expression)?;
29239        self.write(")");
29240        Ok(())
29241    }
29242
29243    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
29244        // CREDENTIALS (key1='value1', key2='value2')
29245        self.write_keyword("CREDENTIALS");
29246        self.write(" (");
29247        for (i, (key, value)) in e.credentials.iter().enumerate() {
29248            if i > 0 {
29249                self.write(", ");
29250            }
29251            self.write(key);
29252            self.write("='");
29253            self.write(value);
29254            self.write("'");
29255        }
29256        self.write(")");
29257        Ok(())
29258    }
29259
29260    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
29261        // CREDENTIALS=(expressions)
29262        self.write_keyword("CREDENTIALS");
29263        self.write("=(");
29264        for (i, expr) in e.expressions.iter().enumerate() {
29265            if i > 0 {
29266                self.write(", ");
29267            }
29268            self.generate_expression(expr)?;
29269        }
29270        self.write(")");
29271        Ok(())
29272    }
29273
29274    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
29275        use crate::dialects::DialectType;
29276
29277        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
29278        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
29279        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
29280            self.generate_expression(&e.this)?;
29281            self.write_space();
29282            self.write_keyword("AS");
29283            self.write_space();
29284            self.generate_identifier(&e.alias)?;
29285            return Ok(());
29286        }
29287        self.write(&e.alias.name);
29288
29289        // BigQuery doesn't support column aliases in CTE definitions
29290        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
29291
29292        if !e.columns.is_empty() && !skip_cte_columns {
29293            self.write("(");
29294            for (i, col) in e.columns.iter().enumerate() {
29295                if i > 0 {
29296                    self.write(", ");
29297                }
29298                self.write(&col.name);
29299            }
29300            self.write(")");
29301        }
29302        // USING KEY (columns) for DuckDB recursive CTEs
29303        if !e.key_expressions.is_empty() {
29304            self.write_space();
29305            self.write_keyword("USING KEY");
29306            self.write(" (");
29307            for (i, key) in e.key_expressions.iter().enumerate() {
29308                if i > 0 {
29309                    self.write(", ");
29310                }
29311                self.write(&key.name);
29312            }
29313            self.write(")");
29314        }
29315        self.write_space();
29316        self.write_keyword("AS");
29317        self.write_space();
29318        if let Some(materialized) = e.materialized {
29319            if materialized {
29320                self.write_keyword("MATERIALIZED");
29321            } else {
29322                self.write_keyword("NOT MATERIALIZED");
29323            }
29324            self.write_space();
29325        }
29326        self.write("(");
29327        self.generate_expression(&e.this)?;
29328        self.write(")");
29329        Ok(())
29330    }
29331
29332    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
29333        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
29334        if e.expressions.is_empty() {
29335            self.write_keyword("WITH CUBE");
29336        } else {
29337            self.write_keyword("CUBE");
29338            self.write("(");
29339            for (i, expr) in e.expressions.iter().enumerate() {
29340                if i > 0 {
29341                    self.write(", ");
29342                }
29343                self.generate_expression(expr)?;
29344            }
29345            self.write(")");
29346        }
29347        Ok(())
29348    }
29349
29350    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
29351        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
29352        self.write_keyword("CURRENT_DATETIME");
29353        if let Some(this) = &e.this {
29354            self.write("(");
29355            self.generate_expression(this)?;
29356            self.write(")");
29357        }
29358        Ok(())
29359    }
29360
29361    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
29362        // CURRENT_SCHEMA - no arguments
29363        self.write_keyword("CURRENT_SCHEMA");
29364        Ok(())
29365    }
29366
29367    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
29368        // CURRENT_SCHEMAS(include_implicit)
29369        self.write_keyword("CURRENT_SCHEMAS");
29370        self.write("(");
29371        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
29372        if !matches!(
29373            self.config.dialect,
29374            Some(crate::dialects::DialectType::Snowflake)
29375        ) {
29376            if let Some(this) = &e.this {
29377                self.generate_expression(this)?;
29378            }
29379        }
29380        self.write(")");
29381        Ok(())
29382    }
29383
29384    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
29385        // CURRENT_USER or CURRENT_USER()
29386        self.write_keyword("CURRENT_USER");
29387        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
29388        let needs_parens = e.this.is_some()
29389            || matches!(
29390                self.config.dialect,
29391                Some(DialectType::Snowflake)
29392                    | Some(DialectType::Spark)
29393                    | Some(DialectType::Hive)
29394                    | Some(DialectType::DuckDB)
29395                    | Some(DialectType::BigQuery)
29396                    | Some(DialectType::MySQL)
29397                    | Some(DialectType::Databricks)
29398            );
29399        if needs_parens {
29400            self.write("()");
29401        }
29402        Ok(())
29403    }
29404
29405    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
29406        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
29407        if self.config.dialect == Some(DialectType::Solr) {
29408            self.generate_expression(&e.this)?;
29409            self.write(" ");
29410            self.write_keyword("OR");
29411            self.write(" ");
29412            self.generate_expression(&e.expression)?;
29413        } else if self.config.dialect == Some(DialectType::MySQL) {
29414            self.generate_mysql_concat_from_dpipe(e)?;
29415        } else {
29416            // String concatenation: this || expression
29417            self.generate_expression(&e.this)?;
29418            self.write(" || ");
29419            self.generate_expression(&e.expression)?;
29420        }
29421        Ok(())
29422    }
29423
29424    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
29425        // DATABLOCKSIZE=... (Teradata)
29426        self.write_keyword("DATABLOCKSIZE");
29427        self.write("=");
29428        if let Some(size) = e.size {
29429            self.write(&size.to_string());
29430            if let Some(units) = &e.units {
29431                self.write_space();
29432                self.generate_expression(units)?;
29433            }
29434        } else if e.minimum.is_some() {
29435            self.write_keyword("MINIMUM");
29436        } else if e.maximum.is_some() {
29437            self.write_keyword("MAXIMUM");
29438        } else if e.default.is_some() {
29439            self.write_keyword("DEFAULT");
29440        }
29441        Ok(())
29442    }
29443
29444    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
29445        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
29446        self.write_keyword("DATA_DELETION");
29447        self.write("=");
29448
29449        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
29450        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
29451
29452        if is_on {
29453            self.write_keyword("ON");
29454            if has_options {
29455                self.write("(");
29456                let mut first = true;
29457                if let Some(filter_column) = &e.filter_column {
29458                    self.write_keyword("FILTER_COLUMN");
29459                    self.write("=");
29460                    self.generate_expression(filter_column)?;
29461                    first = false;
29462                }
29463                if let Some(retention_period) = &e.retention_period {
29464                    if !first {
29465                        self.write(", ");
29466                    }
29467                    self.write_keyword("RETENTION_PERIOD");
29468                    self.write("=");
29469                    self.generate_expression(retention_period)?;
29470                }
29471                self.write(")");
29472            }
29473        } else {
29474            self.write_keyword("OFF");
29475        }
29476        Ok(())
29477    }
29478
29479    /// Generate a Date function expression
29480    /// For Exasol: {d'value'} -> TO_DATE('value')
29481    /// For other dialects: DATE('value')
29482    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
29483        use crate::dialects::DialectType;
29484        use crate::expressions::Literal;
29485
29486        match self.config.dialect {
29487            // Exasol uses TO_DATE for Date expressions
29488            Some(DialectType::Exasol) => {
29489                self.write_keyword("TO_DATE");
29490                self.write("(");
29491                // Extract the string value from the expression if it's a string literal
29492                match &e.this {
29493                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
29494                        let Literal::String(s) = lit.as_ref() else {
29495                            unreachable!()
29496                        };
29497                        self.write("'");
29498                        self.write(s);
29499                        self.write("'");
29500                    }
29501                    _ => {
29502                        self.generate_expression(&e.this)?;
29503                    }
29504                }
29505                self.write(")");
29506            }
29507            // Standard: DATE(value)
29508            _ => {
29509                self.write_keyword("DATE");
29510                self.write("(");
29511                self.generate_expression(&e.this)?;
29512                self.write(")");
29513            }
29514        }
29515        Ok(())
29516    }
29517
29518    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
29519        // DATE_BIN(interval, timestamp[, origin])
29520        self.write_keyword("DATE_BIN");
29521        self.write("(");
29522        self.generate_expression(&e.this)?;
29523        self.write(", ");
29524        self.generate_expression(&e.expression)?;
29525        if let Some(origin) = &e.origin {
29526            self.write(", ");
29527            self.generate_expression(origin)?;
29528        }
29529        self.write(")");
29530        Ok(())
29531    }
29532
29533    fn generate_date_format_column_constraint(
29534        &mut self,
29535        e: &DateFormatColumnConstraint,
29536    ) -> Result<()> {
29537        // FORMAT 'format_string' (Teradata)
29538        self.write_keyword("FORMAT");
29539        self.write_space();
29540        self.generate_expression(&e.this)?;
29541        Ok(())
29542    }
29543
29544    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
29545        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
29546        self.write_keyword("DATE_FROM_PARTS");
29547        self.write("(");
29548        let mut first = true;
29549        if let Some(year) = &e.year {
29550            self.generate_expression(year)?;
29551            first = false;
29552        }
29553        if let Some(month) = &e.month {
29554            if !first {
29555                self.write(", ");
29556            }
29557            self.generate_expression(month)?;
29558            first = false;
29559        }
29560        if let Some(day) = &e.day {
29561            if !first {
29562                self.write(", ");
29563            }
29564            self.generate_expression(day)?;
29565        }
29566        self.write(")");
29567        Ok(())
29568    }
29569
29570    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
29571        // DATETIME(this) or DATETIME(this, expression)
29572        self.write_keyword("DATETIME");
29573        self.write("(");
29574        self.generate_expression(&e.this)?;
29575        if let Some(expr) = &e.expression {
29576            self.write(", ");
29577            self.generate_expression(expr)?;
29578        }
29579        self.write(")");
29580        Ok(())
29581    }
29582
29583    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
29584        // DATETIME_ADD(this, expression, unit)
29585        self.write_keyword("DATETIME_ADD");
29586        self.write("(");
29587        self.generate_expression(&e.this)?;
29588        self.write(", ");
29589        self.generate_expression(&e.expression)?;
29590        if let Some(unit) = &e.unit {
29591            self.write(", ");
29592            self.write_keyword(unit);
29593        }
29594        self.write(")");
29595        Ok(())
29596    }
29597
29598    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
29599        // DATETIME_DIFF(this, expression, unit)
29600        self.write_keyword("DATETIME_DIFF");
29601        self.write("(");
29602        self.generate_expression(&e.this)?;
29603        self.write(", ");
29604        self.generate_expression(&e.expression)?;
29605        if let Some(unit) = &e.unit {
29606            self.write(", ");
29607            self.write_keyword(unit);
29608        }
29609        self.write(")");
29610        Ok(())
29611    }
29612
29613    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
29614        // DATETIME_SUB(this, expression, unit)
29615        self.write_keyword("DATETIME_SUB");
29616        self.write("(");
29617        self.generate_expression(&e.this)?;
29618        self.write(", ");
29619        self.generate_expression(&e.expression)?;
29620        if let Some(unit) = &e.unit {
29621            self.write(", ");
29622            self.write_keyword(unit);
29623        }
29624        self.write(")");
29625        Ok(())
29626    }
29627
29628    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
29629        // DATETIME_TRUNC(this, unit, zone)
29630        self.write_keyword("DATETIME_TRUNC");
29631        self.write("(");
29632        self.generate_expression(&e.this)?;
29633        self.write(", ");
29634        self.write_keyword(&e.unit);
29635        if let Some(zone) = &e.zone {
29636            self.write(", ");
29637            self.generate_expression(zone)?;
29638        }
29639        self.write(")");
29640        Ok(())
29641    }
29642
29643    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
29644        // DAYNAME(this)
29645        self.write_keyword("DAYNAME");
29646        self.write("(");
29647        self.generate_expression(&e.this)?;
29648        self.write(")");
29649        Ok(())
29650    }
29651
29652    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
29653        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
29654        self.write_keyword("DECLARE");
29655        self.write_space();
29656        if e.replace {
29657            self.write_keyword("OR");
29658            self.write_space();
29659            self.write_keyword("REPLACE");
29660            self.write_space();
29661        }
29662        for (i, expr) in e.expressions.iter().enumerate() {
29663            if i > 0 {
29664                self.write(", ");
29665            }
29666            self.generate_expression(expr)?;
29667        }
29668        Ok(())
29669    }
29670
29671    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
29672        use crate::dialects::DialectType;
29673
29674        // variable TYPE [DEFAULT default]
29675        self.generate_expression(&e.this)?;
29676        // BigQuery multi-variable: DECLARE X, Y, Z INT64
29677        for name in &e.additional_names {
29678            self.write(", ");
29679            self.generate_expression(name)?;
29680        }
29681        if let Some(kind) = &e.kind {
29682            self.write_space();
29683            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
29684            // TSQL: Always includes AS (normalization)
29685            // Others: Include AS if present in original
29686            match self.config.dialect {
29687                Some(DialectType::BigQuery) => {
29688                    self.write(kind);
29689                }
29690                Some(DialectType::TSQL) => {
29691                    // TSQL DECLARE: no AS keyword (sqlglot convention)
29692                    // Normalize INT to INTEGER for simple declarations
29693                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
29694                    let is_complex_table = kind.starts_with("TABLE")
29695                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
29696                    if is_complex_table {
29697                        self.write(kind);
29698                    } else if kind == "INT" {
29699                        self.write("INTEGER");
29700                    } else if kind.starts_with("TABLE") {
29701                        // Normalize INT to INTEGER inside simple TABLE column definitions
29702                        let normalized = kind
29703                            .replace(" INT ", " INTEGER ")
29704                            .replace(" INT,", " INTEGER,")
29705                            .replace(" INT)", " INTEGER)")
29706                            .replace("(INT ", "(INTEGER ");
29707                        self.write(&normalized);
29708                    } else {
29709                        self.write(kind);
29710                    }
29711                }
29712                _ => {
29713                    if e.has_as {
29714                        self.write_keyword("AS");
29715                        self.write_space();
29716                    }
29717                    self.write(kind);
29718                }
29719            }
29720        }
29721        if let Some(default) = &e.default {
29722            // BigQuery uses DEFAULT, others use =
29723            match self.config.dialect {
29724                Some(DialectType::BigQuery) => {
29725                    self.write_space();
29726                    self.write_keyword("DEFAULT");
29727                    self.write_space();
29728                }
29729                _ => {
29730                    self.write(" = ");
29731                }
29732            }
29733            self.generate_expression(default)?;
29734        }
29735        Ok(())
29736    }
29737
29738    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
29739        // DECODE(expr, search1, result1, search2, result2, ..., default)
29740        self.write_keyword("DECODE");
29741        self.write("(");
29742        for (i, expr) in e.expressions.iter().enumerate() {
29743            if i > 0 {
29744                self.write(", ");
29745            }
29746            self.generate_expression(expr)?;
29747        }
29748        self.write(")");
29749        Ok(())
29750    }
29751
29752    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
29753        // DECOMPRESS(expr, 'method')
29754        self.write_keyword("DECOMPRESS");
29755        self.write("(");
29756        self.generate_expression(&e.this)?;
29757        self.write(", '");
29758        self.write(&e.method);
29759        self.write("')");
29760        Ok(())
29761    }
29762
29763    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
29764        // DECOMPRESS(expr, 'method')
29765        self.write_keyword("DECOMPRESS");
29766        self.write("(");
29767        self.generate_expression(&e.this)?;
29768        self.write(", '");
29769        self.write(&e.method);
29770        self.write("')");
29771        Ok(())
29772    }
29773
29774    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
29775        // DECRYPT(value, passphrase [, aad [, algorithm]])
29776        self.write_keyword("DECRYPT");
29777        self.write("(");
29778        self.generate_expression(&e.this)?;
29779        if let Some(passphrase) = &e.passphrase {
29780            self.write(", ");
29781            self.generate_expression(passphrase)?;
29782        }
29783        if let Some(aad) = &e.aad {
29784            self.write(", ");
29785            self.generate_expression(aad)?;
29786        }
29787        if let Some(method) = &e.encryption_method {
29788            self.write(", ");
29789            self.generate_expression(method)?;
29790        }
29791        self.write(")");
29792        Ok(())
29793    }
29794
29795    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
29796        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
29797        self.write_keyword("DECRYPT_RAW");
29798        self.write("(");
29799        self.generate_expression(&e.this)?;
29800        if let Some(key) = &e.key {
29801            self.write(", ");
29802            self.generate_expression(key)?;
29803        }
29804        if let Some(iv) = &e.iv {
29805            self.write(", ");
29806            self.generate_expression(iv)?;
29807        }
29808        if let Some(aad) = &e.aad {
29809            self.write(", ");
29810            self.generate_expression(aad)?;
29811        }
29812        if let Some(method) = &e.encryption_method {
29813            self.write(", ");
29814            self.generate_expression(method)?;
29815        }
29816        self.write(")");
29817        Ok(())
29818    }
29819
29820    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
29821        // DEFINER = user
29822        self.write_keyword("DEFINER");
29823        self.write(" = ");
29824        self.generate_expression(&e.this)?;
29825        Ok(())
29826    }
29827
29828    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
29829        // Python: DETACH[DATABASE IF EXISTS] this
29830        self.write_keyword("DETACH");
29831        if e.exists {
29832            self.write_keyword(" DATABASE IF EXISTS");
29833        }
29834        self.write_space();
29835        self.generate_expression(&e.this)?;
29836        Ok(())
29837    }
29838
29839    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
29840        let property_name = match e.this.as_ref() {
29841            Expression::Identifier(id) => id.name.as_str(),
29842            Expression::Var(v) => v.this.as_str(),
29843            _ => "DICTIONARY",
29844        };
29845        self.write_keyword(property_name);
29846        self.write("(");
29847        self.write(&e.kind);
29848        if let Some(settings) = &e.settings {
29849            self.write("(");
29850            if let Expression::Tuple(t) = settings.as_ref() {
29851                if self.config.pretty && !t.expressions.is_empty() {
29852                    self.write_newline();
29853                    self.indent_level += 1;
29854                    for (i, pair) in t.expressions.iter().enumerate() {
29855                        if i > 0 {
29856                            self.write(",");
29857                            self.write_newline();
29858                        }
29859                        self.write_indent();
29860                        if let Expression::Tuple(pair_tuple) = pair {
29861                            if let Some(k) = pair_tuple.expressions.first() {
29862                                self.generate_expression(k)?;
29863                            }
29864                            if let Some(v) = pair_tuple.expressions.get(1) {
29865                                self.write(" ");
29866                                self.generate_expression(v)?;
29867                            }
29868                        } else {
29869                            self.generate_expression(pair)?;
29870                        }
29871                    }
29872                    self.indent_level -= 1;
29873                    self.write_newline();
29874                    self.write_indent();
29875                } else {
29876                    for (i, pair) in t.expressions.iter().enumerate() {
29877                        if i > 0 {
29878                            // ClickHouse dict properties are space-separated, not comma-separated
29879                            self.write(" ");
29880                        }
29881                        if let Expression::Tuple(pair_tuple) = pair {
29882                            if let Some(k) = pair_tuple.expressions.first() {
29883                                self.generate_expression(k)?;
29884                            }
29885                            if let Some(v) = pair_tuple.expressions.get(1) {
29886                                self.write(" ");
29887                                self.generate_expression(v)?;
29888                            }
29889                        } else {
29890                            self.generate_expression(pair)?;
29891                        }
29892                    }
29893                }
29894            } else {
29895                self.generate_expression(settings)?;
29896            }
29897            self.write(")");
29898        } else {
29899            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
29900            self.write("()");
29901        }
29902        self.write(")");
29903        Ok(())
29904    }
29905
29906    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
29907        let property_name = match e.this.as_ref() {
29908            Expression::Identifier(id) => id.name.as_str(),
29909            Expression::Var(v) => v.this.as_str(),
29910            _ => "RANGE",
29911        };
29912        self.write_keyword(property_name);
29913        self.write("(");
29914        if let Some(min) = &e.min {
29915            self.write_keyword("MIN");
29916            self.write_space();
29917            self.generate_expression(min)?;
29918        }
29919        if let Some(max) = &e.max {
29920            self.write_space();
29921            self.write_keyword("MAX");
29922            self.write_space();
29923            self.generate_expression(max)?;
29924        }
29925        self.write(")");
29926        Ok(())
29927    }
29928
29929    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
29930        // Python: {local}DIRECTORY {this}{row_format}
29931        if e.local.is_some() {
29932            self.write_keyword("LOCAL ");
29933        }
29934        self.write_keyword("DIRECTORY");
29935        self.write_space();
29936        self.generate_expression(&e.this)?;
29937        if let Some(row_format) = &e.row_format {
29938            self.write_space();
29939            self.generate_expression(row_format)?;
29940        }
29941        Ok(())
29942    }
29943
29944    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
29945        // Redshift: DISTKEY(column)
29946        self.write_keyword("DISTKEY");
29947        self.write("(");
29948        self.generate_expression(&e.this)?;
29949        self.write(")");
29950        Ok(())
29951    }
29952
29953    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
29954        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
29955        self.write_keyword("DISTSTYLE");
29956        self.write_space();
29957        self.generate_expression(&e.this)?;
29958        Ok(())
29959    }
29960
29961    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
29962        // Python: "DISTRIBUTE BY" expressions
29963        self.write_keyword("DISTRIBUTE BY");
29964        self.write_space();
29965        for (i, expr) in e.expressions.iter().enumerate() {
29966            if i > 0 {
29967                self.write(", ");
29968            }
29969            self.generate_expression(expr)?;
29970        }
29971        Ok(())
29972    }
29973
29974    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
29975        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
29976        self.write_keyword("DISTRIBUTED BY");
29977        self.write_space();
29978        self.write(&e.kind);
29979        if !e.expressions.is_empty() {
29980            self.write(" (");
29981            for (i, expr) in e.expressions.iter().enumerate() {
29982                if i > 0 {
29983                    self.write(", ");
29984                }
29985                self.generate_expression(expr)?;
29986            }
29987            self.write(")");
29988        }
29989        if let Some(buckets) = &e.buckets {
29990            self.write_space();
29991            self.write_keyword("BUCKETS");
29992            self.write_space();
29993            self.generate_expression(buckets)?;
29994        }
29995        if let Some(order) = &e.order {
29996            self.write_space();
29997            self.generate_expression(order)?;
29998        }
29999        Ok(())
30000    }
30001
30002    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
30003        // DOT_PRODUCT(vector1, vector2)
30004        self.write_keyword("DOT_PRODUCT");
30005        self.write("(");
30006        self.generate_expression(&e.this)?;
30007        self.write(", ");
30008        self.generate_expression(&e.expression)?;
30009        self.write(")");
30010        Ok(())
30011    }
30012
30013    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
30014        // Python: DROP{IF EXISTS }expressions
30015        self.write_keyword("DROP");
30016        if e.exists {
30017            self.write_keyword(" IF EXISTS ");
30018        } else {
30019            self.write_space();
30020        }
30021        for (i, expr) in e.expressions.iter().enumerate() {
30022            if i > 0 {
30023                self.write(", ");
30024            }
30025            self.generate_expression(expr)?;
30026        }
30027        Ok(())
30028    }
30029
30030    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
30031        // Python: DUPLICATE KEY (expressions)
30032        self.write_keyword("DUPLICATE KEY");
30033        self.write(" (");
30034        for (i, expr) in e.expressions.iter().enumerate() {
30035            if i > 0 {
30036                self.write(", ");
30037            }
30038            self.generate_expression(expr)?;
30039        }
30040        self.write(")");
30041        Ok(())
30042    }
30043
30044    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
30045        // ELT(index, str1, str2, ...)
30046        self.write_keyword("ELT");
30047        self.write("(");
30048        self.generate_expression(&e.this)?;
30049        for expr in &e.expressions {
30050            self.write(", ");
30051            self.generate_expression(expr)?;
30052        }
30053        self.write(")");
30054        Ok(())
30055    }
30056
30057    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
30058        // ENCODE(string, charset)
30059        self.write_keyword("ENCODE");
30060        self.write("(");
30061        self.generate_expression(&e.this)?;
30062        if let Some(charset) = &e.charset {
30063            self.write(", ");
30064            self.generate_expression(charset)?;
30065        }
30066        self.write(")");
30067        Ok(())
30068    }
30069
30070    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
30071        // Python: [KEY ]ENCODE this [properties]
30072        if e.key.is_some() {
30073            self.write_keyword("KEY ");
30074        }
30075        self.write_keyword("ENCODE");
30076        self.write_space();
30077        self.generate_expression(&e.this)?;
30078        if !e.properties.is_empty() {
30079            self.write(" (");
30080            for (i, prop) in e.properties.iter().enumerate() {
30081                if i > 0 {
30082                    self.write(", ");
30083                }
30084                self.generate_expression(prop)?;
30085            }
30086            self.write(")");
30087        }
30088        Ok(())
30089    }
30090
30091    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
30092        // ENCRYPT(value, passphrase [, aad [, algorithm]])
30093        self.write_keyword("ENCRYPT");
30094        self.write("(");
30095        self.generate_expression(&e.this)?;
30096        if let Some(passphrase) = &e.passphrase {
30097            self.write(", ");
30098            self.generate_expression(passphrase)?;
30099        }
30100        if let Some(aad) = &e.aad {
30101            self.write(", ");
30102            self.generate_expression(aad)?;
30103        }
30104        if let Some(method) = &e.encryption_method {
30105            self.write(", ");
30106            self.generate_expression(method)?;
30107        }
30108        self.write(")");
30109        Ok(())
30110    }
30111
30112    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
30113        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
30114        self.write_keyword("ENCRYPT_RAW");
30115        self.write("(");
30116        self.generate_expression(&e.this)?;
30117        if let Some(key) = &e.key {
30118            self.write(", ");
30119            self.generate_expression(key)?;
30120        }
30121        if let Some(iv) = &e.iv {
30122            self.write(", ");
30123            self.generate_expression(iv)?;
30124        }
30125        if let Some(aad) = &e.aad {
30126            self.write(", ");
30127            self.generate_expression(aad)?;
30128        }
30129        if let Some(method) = &e.encryption_method {
30130            self.write(", ");
30131            self.generate_expression(method)?;
30132        }
30133        self.write(")");
30134        Ok(())
30135    }
30136
30137    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
30138        // MySQL: ENGINE = InnoDB
30139        self.write_keyword("ENGINE");
30140        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
30141            self.write("=");
30142        } else {
30143            self.write(" = ");
30144        }
30145        self.generate_expression(&e.this)?;
30146        Ok(())
30147    }
30148
30149    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
30150        // ENVIRONMENT (expressions)
30151        self.write_keyword("ENVIRONMENT");
30152        self.write(" (");
30153        for (i, expr) in e.expressions.iter().enumerate() {
30154            if i > 0 {
30155                self.write(", ");
30156            }
30157            self.generate_expression(expr)?;
30158        }
30159        self.write(")");
30160        Ok(())
30161    }
30162
30163    fn generate_ephemeral_column_constraint(
30164        &mut self,
30165        e: &EphemeralColumnConstraint,
30166    ) -> Result<()> {
30167        // MySQL: EPHEMERAL [expr]
30168        self.write_keyword("EPHEMERAL");
30169        if let Some(this) = &e.this {
30170            self.write_space();
30171            self.generate_expression(this)?;
30172        }
30173        Ok(())
30174    }
30175
30176    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
30177        // Snowflake: EQUAL_NULL(a, b)
30178        self.write_keyword("EQUAL_NULL");
30179        self.write("(");
30180        self.generate_expression(&e.this)?;
30181        self.write(", ");
30182        self.generate_expression(&e.expression)?;
30183        self.write(")");
30184        Ok(())
30185    }
30186
30187    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
30188        use crate::dialects::DialectType;
30189
30190        // PostgreSQL uses <-> operator syntax
30191        match self.config.dialect {
30192            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
30193                self.generate_expression(&e.this)?;
30194                self.write(" <-> ");
30195                self.generate_expression(&e.expression)?;
30196            }
30197            _ => {
30198                // Other dialects use EUCLIDEAN_DISTANCE function
30199                self.write_keyword("EUCLIDEAN_DISTANCE");
30200                self.write("(");
30201                self.generate_expression(&e.this)?;
30202                self.write(", ");
30203                self.generate_expression(&e.expression)?;
30204                self.write(")");
30205            }
30206        }
30207        Ok(())
30208    }
30209
30210    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
30211        // EXECUTE AS CALLER|OWNER|user
30212        self.write_keyword("EXECUTE AS");
30213        self.write_space();
30214        self.generate_expression(&e.this)?;
30215        Ok(())
30216    }
30217
30218    fn generate_export(&mut self, e: &Export) -> Result<()> {
30219        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
30220        self.write_keyword("EXPORT DATA");
30221        if let Some(connection) = &e.connection {
30222            self.write_space();
30223            self.write_keyword("WITH CONNECTION");
30224            self.write_space();
30225            self.generate_expression(connection)?;
30226        }
30227        if !e.options.is_empty() {
30228            self.write_space();
30229            self.generate_options_clause(&e.options)?;
30230        }
30231        self.write_space();
30232        self.write_keyword("AS");
30233        self.write_space();
30234        self.generate_expression(&e.this)?;
30235        Ok(())
30236    }
30237
30238    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
30239        // EXTERNAL [this]
30240        self.write_keyword("EXTERNAL");
30241        if let Some(this) = &e.this {
30242            self.write_space();
30243            self.generate_expression(this)?;
30244        }
30245        Ok(())
30246    }
30247
30248    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
30249        // Python: {no}FALLBACK{protection}
30250        if e.no.is_some() {
30251            self.write_keyword("NO ");
30252        }
30253        self.write_keyword("FALLBACK");
30254        if e.protection.is_some() {
30255            self.write_keyword(" PROTECTION");
30256        }
30257        Ok(())
30258    }
30259
30260    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
30261        // BigQuery: FARM_FINGERPRINT(value)
30262        self.write_keyword("FARM_FINGERPRINT");
30263        self.write("(");
30264        for (i, expr) in e.expressions.iter().enumerate() {
30265            if i > 0 {
30266                self.write(", ");
30267            }
30268            self.generate_expression(expr)?;
30269        }
30270        self.write(")");
30271        Ok(())
30272    }
30273
30274    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
30275        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
30276        self.write_keyword("FEATURES_AT_TIME");
30277        self.write("(");
30278        self.generate_expression(&e.this)?;
30279        if let Some(time) = &e.time {
30280            self.write(", ");
30281            self.generate_expression(time)?;
30282        }
30283        if let Some(num_rows) = &e.num_rows {
30284            self.write(", ");
30285            self.generate_expression(num_rows)?;
30286        }
30287        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
30288            self.write(", ");
30289            self.generate_expression(ignore_nulls)?;
30290        }
30291        self.write(")");
30292        Ok(())
30293    }
30294
30295    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
30296        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
30297        let use_limit = !e.percent
30298            && !e.with_ties
30299            && e.count.is_some()
30300            && matches!(
30301                self.config.dialect,
30302                Some(DialectType::Spark)
30303                    | Some(DialectType::Hive)
30304                    | Some(DialectType::DuckDB)
30305                    | Some(DialectType::SQLite)
30306                    | Some(DialectType::MySQL)
30307                    | Some(DialectType::BigQuery)
30308                    | Some(DialectType::Databricks)
30309                    | Some(DialectType::StarRocks)
30310                    | Some(DialectType::Doris)
30311                    | Some(DialectType::Athena)
30312                    | Some(DialectType::ClickHouse)
30313            );
30314
30315        if use_limit {
30316            self.write_keyword("LIMIT");
30317            self.write_space();
30318            self.generate_expression(e.count.as_ref().unwrap())?;
30319            return Ok(());
30320        }
30321
30322        // Python: FETCH direction count limit_options
30323        self.write_keyword("FETCH");
30324        if !e.direction.is_empty() {
30325            self.write_space();
30326            self.write_keyword(&e.direction);
30327        }
30328        if let Some(count) = &e.count {
30329            self.write_space();
30330            self.generate_expression(count)?;
30331        }
30332        // Generate PERCENT, ROWS, WITH TIES/ONLY
30333        if e.percent {
30334            self.write_keyword(" PERCENT");
30335        }
30336        if e.rows {
30337            self.write_keyword(" ROWS");
30338        }
30339        if e.with_ties {
30340            self.write_keyword(" WITH TIES");
30341        } else if e.rows {
30342            self.write_keyword(" ONLY");
30343        } else {
30344            self.write_keyword(" ROWS ONLY");
30345        }
30346        Ok(())
30347    }
30348
30349    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
30350        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
30351        // For Spark/Databricks without hive_format: USING this
30352        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
30353        if e.hive_format.is_some() {
30354            // Hive format: STORED AS ...
30355            self.write_keyword("STORED AS");
30356            self.write_space();
30357            if let Some(this) = &e.this {
30358                // Uppercase the format name (e.g., parquet -> PARQUET)
30359                if let Expression::Identifier(id) = this.as_ref() {
30360                    self.write_keyword(&id.name.to_ascii_uppercase());
30361                } else {
30362                    self.generate_expression(this)?;
30363                }
30364            }
30365        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
30366            // Hive: STORED AS format
30367            self.write_keyword("STORED AS");
30368            self.write_space();
30369            if let Some(this) = &e.this {
30370                if let Expression::Identifier(id) = this.as_ref() {
30371                    self.write_keyword(&id.name.to_ascii_uppercase());
30372                } else {
30373                    self.generate_expression(this)?;
30374                }
30375            }
30376        } else if matches!(
30377            self.config.dialect,
30378            Some(DialectType::Spark) | Some(DialectType::Databricks)
30379        ) {
30380            // Spark/Databricks: USING format (e.g., USING DELTA)
30381            self.write_keyword("USING");
30382            self.write_space();
30383            if let Some(this) = &e.this {
30384                self.generate_expression(this)?;
30385            }
30386        } else {
30387            // Snowflake/standard format
30388            self.write_keyword("FILE_FORMAT");
30389            self.write(" = ");
30390            if let Some(this) = &e.this {
30391                self.generate_expression(this)?;
30392            } else if !e.expressions.is_empty() {
30393                self.write("(");
30394                for (i, expr) in e.expressions.iter().enumerate() {
30395                    if i > 0 {
30396                        self.write(", ");
30397                    }
30398                    self.generate_expression(expr)?;
30399                }
30400                self.write(")");
30401            }
30402        }
30403        Ok(())
30404    }
30405
30406    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
30407        // agg_func FILTER(WHERE condition)
30408        self.generate_expression(&e.this)?;
30409        self.write_space();
30410        self.write_keyword("FILTER");
30411        self.write("(");
30412        self.write_keyword("WHERE");
30413        self.write_space();
30414        self.generate_expression(&e.expression)?;
30415        self.write(")");
30416        Ok(())
30417    }
30418
30419    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
30420        // FLOAT64(this) or FLOAT64(this, expression)
30421        self.write_keyword("FLOAT64");
30422        self.write("(");
30423        self.generate_expression(&e.this)?;
30424        if let Some(expr) = &e.expression {
30425            self.write(", ");
30426            self.generate_expression(expr)?;
30427        }
30428        self.write(")");
30429        Ok(())
30430    }
30431
30432    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
30433        // FOR this DO expression
30434        self.write_keyword("FOR");
30435        self.write_space();
30436        self.generate_expression(&e.this)?;
30437        self.write_space();
30438        self.write_keyword("DO");
30439        self.write_space();
30440        self.generate_expression(&e.expression)?;
30441        Ok(())
30442    }
30443
30444    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
30445        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
30446        self.write_keyword("FOREIGN KEY");
30447        if !e.expressions.is_empty() {
30448            self.write(" (");
30449            for (i, expr) in e.expressions.iter().enumerate() {
30450                if i > 0 {
30451                    self.write(", ");
30452                }
30453                self.generate_expression(expr)?;
30454            }
30455            self.write(")");
30456        }
30457        if let Some(reference) = &e.reference {
30458            self.write_space();
30459            self.generate_expression(reference)?;
30460        }
30461        if let Some(delete) = &e.delete {
30462            self.write_space();
30463            self.write_keyword("ON DELETE");
30464            self.write_space();
30465            self.generate_expression(delete)?;
30466        }
30467        if let Some(update) = &e.update {
30468            self.write_space();
30469            self.write_keyword("ON UPDATE");
30470            self.write_space();
30471            self.generate_expression(update)?;
30472        }
30473        if !e.options.is_empty() {
30474            self.write_space();
30475            for (i, opt) in e.options.iter().enumerate() {
30476                if i > 0 {
30477                    self.write_space();
30478                }
30479                self.generate_expression(opt)?;
30480            }
30481        }
30482        Ok(())
30483    }
30484
30485    fn generate_format(&mut self, e: &Format) -> Result<()> {
30486        // FORMAT(this, expressions...)
30487        self.write_keyword("FORMAT");
30488        self.write("(");
30489        self.generate_expression(&e.this)?;
30490        for expr in &e.expressions {
30491            self.write(", ");
30492            self.generate_expression(expr)?;
30493        }
30494        self.write(")");
30495        Ok(())
30496    }
30497
30498    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
30499        // Teradata: column (FORMAT 'format_string')
30500        self.generate_expression(&e.this)?;
30501        self.write(" (");
30502        self.write_keyword("FORMAT");
30503        self.write(" '");
30504        self.write(&e.format);
30505        self.write("')");
30506        Ok(())
30507    }
30508
30509    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
30510        // Python: FREESPACE=this[PERCENT]
30511        self.write_keyword("FREESPACE");
30512        self.write("=");
30513        self.generate_expression(&e.this)?;
30514        if e.percent.is_some() {
30515            self.write_keyword(" PERCENT");
30516        }
30517        Ok(())
30518    }
30519
30520    fn generate_from(&mut self, e: &From) -> Result<()> {
30521        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
30522        self.write_keyword("FROM");
30523        self.write_space();
30524
30525        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
30526        // But keep commas when TABLESAMPLE is present
30527        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
30528        use crate::dialects::DialectType;
30529        let has_tablesample = e
30530            .expressions
30531            .iter()
30532            .any(|expr| matches!(expr, Expression::TableSample(_)));
30533        let is_cross_join_dialect = matches!(
30534            self.config.dialect,
30535            Some(DialectType::BigQuery)
30536                | Some(DialectType::Hive)
30537                | Some(DialectType::Spark)
30538                | Some(DialectType::Databricks)
30539                | Some(DialectType::SQLite)
30540                | Some(DialectType::ClickHouse)
30541        );
30542        let source_is_same_as_target2 = self.config.source_dialect.is_some()
30543            && self.config.source_dialect == self.config.dialect;
30544        let source_is_cross_join_dialect2 = matches!(
30545            self.config.source_dialect,
30546            Some(DialectType::BigQuery)
30547                | Some(DialectType::Hive)
30548                | Some(DialectType::Spark)
30549                | Some(DialectType::Databricks)
30550                | Some(DialectType::SQLite)
30551                | Some(DialectType::ClickHouse)
30552        );
30553        let use_cross_join = !has_tablesample
30554            && is_cross_join_dialect
30555            && (source_is_same_as_target2
30556                || source_is_cross_join_dialect2
30557                || self.config.source_dialect.is_none());
30558
30559        // Snowflake wraps standalone VALUES in FROM clause with parentheses
30560        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
30561
30562        for (i, expr) in e.expressions.iter().enumerate() {
30563            if i > 0 {
30564                if use_cross_join {
30565                    self.write(" CROSS JOIN ");
30566                } else {
30567                    self.write(", ");
30568                }
30569            }
30570            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
30571                self.write("(");
30572                self.generate_expression(expr)?;
30573                self.write(")");
30574            } else {
30575                self.generate_expression(expr)?;
30576            }
30577            // Output leading comments that were on the table name before FROM
30578            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
30579            let leading = Self::extract_table_leading_comments(expr);
30580            for comment in &leading {
30581                self.write_space();
30582                self.write_formatted_comment(comment);
30583            }
30584        }
30585        Ok(())
30586    }
30587
30588    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
30589    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
30590        match expr {
30591            Expression::Table(t) => t.leading_comments.clone(),
30592            Expression::Pivot(p) => {
30593                if let Expression::Table(t) = &p.this {
30594                    t.leading_comments.clone()
30595                } else {
30596                    Vec::new()
30597                }
30598            }
30599            _ => Vec::new(),
30600        }
30601    }
30602
30603    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
30604        // FROM_BASE(this, expression) - convert from base N
30605        self.write_keyword("FROM_BASE");
30606        self.write("(");
30607        self.generate_expression(&e.this)?;
30608        self.write(", ");
30609        self.generate_expression(&e.expression)?;
30610        self.write(")");
30611        Ok(())
30612    }
30613
30614    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
30615        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
30616        self.generate_expression(&e.this)?;
30617        if let Some(zone) = &e.zone {
30618            self.write_space();
30619            self.write_keyword("AT TIME ZONE");
30620            self.write_space();
30621            self.generate_expression(zone)?;
30622            self.write_space();
30623            self.write_keyword("AT TIME ZONE");
30624            self.write(" 'UTC'");
30625        }
30626        Ok(())
30627    }
30628
30629    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
30630        // GAP_FILL(this, ts_column, bucket_width, ...)
30631        self.write_keyword("GAP_FILL");
30632        self.write("(");
30633        self.generate_expression(&e.this)?;
30634        if let Some(ts_column) = &e.ts_column {
30635            self.write(", ");
30636            self.generate_expression(ts_column)?;
30637        }
30638        if let Some(bucket_width) = &e.bucket_width {
30639            self.write(", ");
30640            self.generate_expression(bucket_width)?;
30641        }
30642        if let Some(partitioning_columns) = &e.partitioning_columns {
30643            self.write(", ");
30644            self.generate_expression(partitioning_columns)?;
30645        }
30646        if let Some(value_columns) = &e.value_columns {
30647            self.write(", ");
30648            self.generate_expression(value_columns)?;
30649        }
30650        self.write(")");
30651        Ok(())
30652    }
30653
30654    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
30655        // GENERATE_DATE_ARRAY(start, end, step)
30656        self.write_keyword("GENERATE_DATE_ARRAY");
30657        self.write("(");
30658        let mut first = true;
30659        if let Some(start) = &e.start {
30660            self.generate_expression(start)?;
30661            first = false;
30662        }
30663        if let Some(end) = &e.end {
30664            if !first {
30665                self.write(", ");
30666            }
30667            self.generate_expression(end)?;
30668            first = false;
30669        }
30670        if let Some(step) = &e.step {
30671            if !first {
30672                self.write(", ");
30673            }
30674            self.generate_expression(step)?;
30675        }
30676        self.write(")");
30677        Ok(())
30678    }
30679
30680    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
30681        // ML.GENERATE_EMBEDDING(model, content, params)
30682        self.write_keyword("ML.GENERATE_EMBEDDING");
30683        self.write("(");
30684        self.generate_expression(&e.this)?;
30685        self.write(", ");
30686        self.generate_expression(&e.expression)?;
30687        if let Some(params) = &e.params_struct {
30688            self.write(", ");
30689            self.generate_expression(params)?;
30690        }
30691        self.write(")");
30692        Ok(())
30693    }
30694
30695    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
30696        // Dialect-specific function name
30697        let fn_name = match self.config.dialect {
30698            Some(DialectType::Presto)
30699            | Some(DialectType::Trino)
30700            | Some(DialectType::Athena)
30701            | Some(DialectType::Spark)
30702            | Some(DialectType::Databricks)
30703            | Some(DialectType::Hive) => "SEQUENCE",
30704            _ => "GENERATE_SERIES",
30705        };
30706        self.write_keyword(fn_name);
30707        self.write("(");
30708        let mut first = true;
30709        if let Some(start) = &e.start {
30710            self.generate_expression(start)?;
30711            first = false;
30712        }
30713        if let Some(end) = &e.end {
30714            if !first {
30715                self.write(", ");
30716            }
30717            self.generate_expression(end)?;
30718            first = false;
30719        }
30720        if let Some(step) = &e.step {
30721            if !first {
30722                self.write(", ");
30723            }
30724            // For Presto/Trino: convert WEEK intervals to DAY multiples
30725            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
30726            if matches!(
30727                self.config.dialect,
30728                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
30729            ) {
30730                if let Some(converted) = self.convert_week_interval_to_day(step) {
30731                    self.generate_expression(&converted)?;
30732                } else {
30733                    self.generate_expression(step)?;
30734                }
30735            } else {
30736                self.generate_expression(step)?;
30737            }
30738        }
30739        self.write(")");
30740        Ok(())
30741    }
30742
30743    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
30744    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
30745    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
30746        use crate::expressions::*;
30747        if let Expression::Interval(ref iv) = expr {
30748            // Check for structured WEEK unit
30749            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
30750                unit: IntervalUnit::Week,
30751                ..
30752            }) = &iv.unit
30753            {
30754                // Value is in iv.this
30755                let count = match &iv.this {
30756                    Some(Expression::Literal(lit)) => match lit.as_ref() {
30757                        Literal::String(s) | Literal::Number(s) => s.clone(),
30758                        _ => return None,
30759                    },
30760                    _ => return None,
30761                };
30762                (true, count)
30763            } else if iv.unit.is_none() {
30764                // Check for string-encoded interval like "1 WEEK"
30765                if let Some(Expression::Literal(lit)) = &iv.this {
30766                    if let Literal::String(s) = lit.as_ref() {
30767                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
30768                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
30769                            (true, parts[0].to_string())
30770                        } else {
30771                            (false, String::new())
30772                        }
30773                    } else {
30774                        (false, String::new())
30775                    }
30776                } else {
30777                    (false, String::new())
30778                }
30779            } else {
30780                (false, String::new())
30781            };
30782
30783            if is_week {
30784                // Build: (N * INTERVAL '7' DAY)
30785                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
30786                let day_interval = Expression::Interval(Box::new(Interval {
30787                    this: Some(Expression::Literal(Box::new(Literal::String(
30788                        "7".to_string(),
30789                    )))),
30790                    unit: Some(IntervalUnitSpec::Simple {
30791                        unit: IntervalUnit::Day,
30792                        use_plural: false,
30793                    }),
30794                }));
30795                let mul = Expression::Mul(Box::new(BinaryOp {
30796                    left: count_expr,
30797                    right: day_interval,
30798                    left_comments: vec![],
30799                    operator_comments: vec![],
30800                    trailing_comments: vec![],
30801                    inferred_type: None,
30802                }));
30803                return Some(Expression::Paren(Box::new(Paren {
30804                    this: mul,
30805                    trailing_comments: vec![],
30806                })));
30807            }
30808        }
30809        None
30810    }
30811
30812    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
30813        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
30814        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
30815        self.write("(");
30816        let mut first = true;
30817        if let Some(start) = &e.start {
30818            self.generate_expression(start)?;
30819            first = false;
30820        }
30821        if let Some(end) = &e.end {
30822            if !first {
30823                self.write(", ");
30824            }
30825            self.generate_expression(end)?;
30826            first = false;
30827        }
30828        if let Some(step) = &e.step {
30829            if !first {
30830                self.write(", ");
30831            }
30832            self.generate_expression(step)?;
30833        }
30834        self.write(")");
30835        Ok(())
30836    }
30837
30838    fn generate_generated_as_identity_column_constraint(
30839        &mut self,
30840        e: &GeneratedAsIdentityColumnConstraint,
30841    ) -> Result<()> {
30842        use crate::dialects::DialectType;
30843
30844        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
30845        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
30846            self.write_keyword("AUTOINCREMENT");
30847            if let Some(start) = &e.start {
30848                self.write_keyword(" START ");
30849                self.generate_expression(start)?;
30850            }
30851            if let Some(increment) = &e.increment {
30852                self.write_keyword(" INCREMENT ");
30853                self.generate_expression(increment)?;
30854            }
30855            return Ok(());
30856        }
30857
30858        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
30859        self.write_keyword("GENERATED");
30860        if let Some(this) = &e.this {
30861            // Check if it's a truthy boolean expression
30862            if let Expression::Boolean(b) = this.as_ref() {
30863                if b.value {
30864                    self.write_keyword(" ALWAYS");
30865                } else {
30866                    self.write_keyword(" BY DEFAULT");
30867                    if e.on_null.is_some() {
30868                        self.write_keyword(" ON NULL");
30869                    }
30870                }
30871            } else {
30872                self.write_keyword(" ALWAYS");
30873            }
30874        }
30875        self.write_keyword(" AS IDENTITY");
30876        // Add sequence options if any
30877        let has_options = e.start.is_some()
30878            || e.increment.is_some()
30879            || e.minvalue.is_some()
30880            || e.maxvalue.is_some();
30881        if has_options {
30882            self.write(" (");
30883            let mut first = true;
30884            if let Some(start) = &e.start {
30885                self.write_keyword("START WITH ");
30886                self.generate_expression(start)?;
30887                first = false;
30888            }
30889            if let Some(increment) = &e.increment {
30890                if !first {
30891                    self.write(" ");
30892                }
30893                self.write_keyword("INCREMENT BY ");
30894                self.generate_expression(increment)?;
30895                first = false;
30896            }
30897            if let Some(minvalue) = &e.minvalue {
30898                if !first {
30899                    self.write(" ");
30900                }
30901                self.write_keyword("MINVALUE ");
30902                self.generate_expression(minvalue)?;
30903                first = false;
30904            }
30905            if let Some(maxvalue) = &e.maxvalue {
30906                if !first {
30907                    self.write(" ");
30908                }
30909                self.write_keyword("MAXVALUE ");
30910                self.generate_expression(maxvalue)?;
30911            }
30912            self.write(")");
30913        }
30914        Ok(())
30915    }
30916
30917    fn generate_generated_as_row_column_constraint(
30918        &mut self,
30919        e: &GeneratedAsRowColumnConstraint,
30920    ) -> Result<()> {
30921        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
30922        self.write_keyword("GENERATED ALWAYS AS ROW ");
30923        if e.start.is_some() {
30924            self.write_keyword("START");
30925        } else {
30926            self.write_keyword("END");
30927        }
30928        if e.hidden.is_some() {
30929            self.write_keyword(" HIDDEN");
30930        }
30931        Ok(())
30932    }
30933
30934    fn generate_get(&mut self, e: &Get) -> Result<()> {
30935        // GET this target properties
30936        self.write_keyword("GET");
30937        self.write_space();
30938        self.generate_expression(&e.this)?;
30939        if let Some(target) = &e.target {
30940            self.write_space();
30941            self.generate_expression(target)?;
30942        }
30943        for prop in &e.properties {
30944            self.write_space();
30945            self.generate_expression(prop)?;
30946        }
30947        Ok(())
30948    }
30949
30950    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
30951        // GetExtract generates bracket access: this[expression]
30952        self.generate_expression(&e.this)?;
30953        self.write("[");
30954        self.generate_expression(&e.expression)?;
30955        self.write("]");
30956        Ok(())
30957    }
30958
30959    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
30960        // GETBIT(this, expression) or GET_BIT(this, expression)
30961        self.write_keyword("GETBIT");
30962        self.write("(");
30963        self.generate_expression(&e.this)?;
30964        self.write(", ");
30965        self.generate_expression(&e.expression)?;
30966        self.write(")");
30967        Ok(())
30968    }
30969
30970    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
30971        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
30972        if e.is_role {
30973            self.write_keyword("ROLE");
30974            self.write_space();
30975        } else if e.is_group {
30976            self.write_keyword("GROUP");
30977            self.write_space();
30978        } else if e.is_share {
30979            self.write_keyword("SHARE");
30980            self.write_space();
30981        }
30982        self.write(&e.name.name);
30983        Ok(())
30984    }
30985
30986    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
30987        // privilege(columns) or just privilege
30988        self.generate_expression(&e.this)?;
30989        if !e.expressions.is_empty() {
30990            self.write("(");
30991            for (i, expr) in e.expressions.iter().enumerate() {
30992                if i > 0 {
30993                    self.write(", ");
30994                }
30995                self.generate_expression(expr)?;
30996            }
30997            self.write(")");
30998        }
30999        Ok(())
31000    }
31001
31002    fn generate_group(&mut self, e: &Group) -> Result<()> {
31003        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
31004        self.write_keyword("GROUP BY");
31005        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
31006        match e.all {
31007            Some(true) => {
31008                self.write_space();
31009                self.write_keyword("ALL");
31010            }
31011            Some(false) => {
31012                self.write_space();
31013                self.write_keyword("DISTINCT");
31014            }
31015            None => {}
31016        }
31017        if !e.expressions.is_empty() {
31018            self.write_space();
31019            for (i, expr) in e.expressions.iter().enumerate() {
31020                if i > 0 {
31021                    self.write(", ");
31022                }
31023                self.generate_expression(expr)?;
31024            }
31025        }
31026        // Handle CUBE, ROLLUP, GROUPING SETS
31027        if let Some(cube) = &e.cube {
31028            if !e.expressions.is_empty() {
31029                self.write(", ");
31030            } else {
31031                self.write_space();
31032            }
31033            self.generate_expression(cube)?;
31034        }
31035        if let Some(rollup) = &e.rollup {
31036            if !e.expressions.is_empty() || e.cube.is_some() {
31037                self.write(", ");
31038            } else {
31039                self.write_space();
31040            }
31041            self.generate_expression(rollup)?;
31042        }
31043        if let Some(grouping_sets) = &e.grouping_sets {
31044            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
31045                self.write(", ");
31046            } else {
31047                self.write_space();
31048            }
31049            self.generate_expression(grouping_sets)?;
31050        }
31051        if let Some(totals) = &e.totals {
31052            self.write_space();
31053            self.write_keyword("WITH TOTALS");
31054            self.generate_expression(totals)?;
31055        }
31056        Ok(())
31057    }
31058
31059    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
31060        // GROUP BY expressions
31061        self.write_keyword("GROUP BY");
31062        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
31063        match e.all {
31064            Some(true) => {
31065                self.write_space();
31066                self.write_keyword("ALL");
31067            }
31068            Some(false) => {
31069                self.write_space();
31070                self.write_keyword("DISTINCT");
31071            }
31072            None => {}
31073        }
31074
31075        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
31076        // These are represented as Cube/Rollup expressions with empty expressions at the end
31077        let mut trailing_cube = false;
31078        let mut trailing_rollup = false;
31079        let mut regular_expressions: Vec<&Expression> = Vec::new();
31080
31081        for expr in &e.expressions {
31082            match expr {
31083                Expression::Cube(c) if c.expressions.is_empty() => {
31084                    trailing_cube = true;
31085                }
31086                Expression::Rollup(r) if r.expressions.is_empty() => {
31087                    trailing_rollup = true;
31088                }
31089                _ => {
31090                    regular_expressions.push(expr);
31091                }
31092            }
31093        }
31094
31095        // In pretty mode, put columns on separate lines
31096        if self.config.pretty {
31097            self.write_newline();
31098            self.indent_level += 1;
31099            for (i, expr) in regular_expressions.iter().enumerate() {
31100                if i > 0 {
31101                    self.write(",");
31102                    self.write_newline();
31103                }
31104                self.write_indent();
31105                self.generate_expression(expr)?;
31106            }
31107            self.indent_level -= 1;
31108        } else {
31109            self.write_space();
31110            for (i, expr) in regular_expressions.iter().enumerate() {
31111                if i > 0 {
31112                    self.write(", ");
31113                }
31114                self.generate_expression(expr)?;
31115            }
31116        }
31117
31118        // Output trailing WITH CUBE or WITH ROLLUP
31119        if trailing_cube {
31120            self.write_space();
31121            self.write_keyword("WITH CUBE");
31122        } else if trailing_rollup {
31123            self.write_space();
31124            self.write_keyword("WITH ROLLUP");
31125        }
31126
31127        // ClickHouse: WITH TOTALS
31128        if e.totals {
31129            self.write_space();
31130            self.write_keyword("WITH TOTALS");
31131        }
31132
31133        Ok(())
31134    }
31135
31136    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
31137        let function_name = if e.expressions.len() > 1
31138            && matches!(
31139                self.config.dialect,
31140                Some(DialectType::TSQL | DialectType::Fabric)
31141            ) {
31142            "GROUPING_ID"
31143        } else {
31144            "GROUPING"
31145        };
31146        self.write_keyword(function_name);
31147        self.write("(");
31148        for (i, expr) in e.expressions.iter().enumerate() {
31149            if i > 0 {
31150                self.write(", ");
31151            }
31152            self.generate_expression(expr)?;
31153        }
31154        self.write(")");
31155        Ok(())
31156    }
31157
31158    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
31159        // GROUPING_ID(col1, col2, ...)
31160        self.write_keyword("GROUPING_ID");
31161        self.write("(");
31162        for (i, expr) in e.expressions.iter().enumerate() {
31163            if i > 0 {
31164                self.write(", ");
31165            }
31166            self.generate_expression(expr)?;
31167        }
31168        self.write(")");
31169        Ok(())
31170    }
31171
31172    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
31173        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
31174        self.write_keyword("GROUPING SETS");
31175        self.write(" (");
31176        for (i, expr) in e.expressions.iter().enumerate() {
31177            if i > 0 {
31178                self.write(", ");
31179            }
31180            self.generate_expression(expr)?;
31181        }
31182        self.write(")");
31183        Ok(())
31184    }
31185
31186    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
31187        // HASH_AGG(this, expressions...)
31188        self.write_keyword("HASH_AGG");
31189        self.write("(");
31190        self.generate_expression(&e.this)?;
31191        for expr in &e.expressions {
31192            self.write(", ");
31193            self.generate_expression(expr)?;
31194        }
31195        self.write(")");
31196        Ok(())
31197    }
31198
31199    fn generate_having(&mut self, e: &Having) -> Result<()> {
31200        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
31201        self.write_keyword("HAVING");
31202        self.write_space();
31203        self.generate_expression(&e.this)?;
31204        Ok(())
31205    }
31206
31207    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
31208        // Python: this HAVING MAX|MIN expression
31209        self.generate_expression(&e.this)?;
31210        self.write_space();
31211        self.write_keyword("HAVING");
31212        self.write_space();
31213        if e.max.is_some() {
31214            self.write_keyword("MAX");
31215        } else {
31216            self.write_keyword("MIN");
31217        }
31218        self.write_space();
31219        self.generate_expression(&e.expression)?;
31220        Ok(())
31221    }
31222
31223    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
31224        use crate::dialects::DialectType;
31225        // DuckDB: convert dollar-tagged strings to single-quoted
31226        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
31227            // Extract the string content and output as single-quoted
31228            if let Expression::Literal(ref lit) = *e.this {
31229                if let Literal::String(ref s) = lit.as_ref() {
31230                    return self.generate_string_literal(s);
31231                }
31232            }
31233        }
31234        // PostgreSQL: preserve dollar-quoting
31235        if matches!(
31236            self.config.dialect,
31237            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31238        ) {
31239            self.write("$");
31240            if let Some(tag) = &e.tag {
31241                self.generate_expression(tag)?;
31242            }
31243            self.write("$");
31244            self.generate_expression(&e.this)?;
31245            self.write("$");
31246            if let Some(tag) = &e.tag {
31247                self.generate_expression(tag)?;
31248            }
31249            self.write("$");
31250            return Ok(());
31251        }
31252        // Default: output as dollar-tagged
31253        self.write("$");
31254        if let Some(tag) = &e.tag {
31255            self.generate_expression(tag)?;
31256        }
31257        self.write("$");
31258        self.generate_expression(&e.this)?;
31259        self.write("$");
31260        if let Some(tag) = &e.tag {
31261            self.generate_expression(tag)?;
31262        }
31263        self.write("$");
31264        Ok(())
31265    }
31266
31267    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
31268        // HEX_ENCODE(this)
31269        self.write_keyword("HEX_ENCODE");
31270        self.write("(");
31271        self.generate_expression(&e.this)?;
31272        self.write(")");
31273        Ok(())
31274    }
31275
31276    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
31277        // Python: this (kind => expression)
31278        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
31279        match e.this.as_ref() {
31280            Expression::Identifier(id) => self.write(&id.name),
31281            other => self.generate_expression(other)?,
31282        }
31283        self.write(" (");
31284        self.write(&e.kind);
31285        self.write(" => ");
31286        self.generate_expression(&e.expression)?;
31287        self.write(")");
31288        Ok(())
31289    }
31290
31291    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
31292        // HLL(this, expressions...)
31293        self.write_keyword("HLL");
31294        self.write("(");
31295        self.generate_expression(&e.this)?;
31296        for expr in &e.expressions {
31297            self.write(", ");
31298            self.generate_expression(expr)?;
31299        }
31300        self.write(")");
31301        Ok(())
31302    }
31303
31304    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
31305        // Python: IN|OUT|IN OUT
31306        if e.input_.is_some() && e.output.is_some() {
31307            self.write_keyword("IN OUT");
31308        } else if e.input_.is_some() {
31309            self.write_keyword("IN");
31310        } else if e.output.is_some() {
31311            self.write_keyword("OUT");
31312        }
31313        Ok(())
31314    }
31315
31316    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
31317        // Python: INCLUDE this [column_def] [AS alias]
31318        self.write_keyword("INCLUDE");
31319        self.write_space();
31320        self.generate_expression(&e.this)?;
31321        if let Some(column_def) = &e.column_def {
31322            self.write_space();
31323            self.generate_expression(column_def)?;
31324        }
31325        if let Some(alias) = &e.alias {
31326            self.write_space();
31327            self.write_keyword("AS");
31328            self.write_space();
31329            self.write(alias);
31330        }
31331        Ok(())
31332    }
31333
31334    fn generate_index(&mut self, e: &Index) -> Result<()> {
31335        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
31336        if e.unique {
31337            self.write_keyword("UNIQUE");
31338            self.write_space();
31339        }
31340        if e.primary.is_some() {
31341            self.write_keyword("PRIMARY");
31342            self.write_space();
31343        }
31344        if e.amp.is_some() {
31345            self.write_keyword("AMP");
31346            self.write_space();
31347        }
31348        if e.table.is_none() {
31349            self.write_keyword("INDEX");
31350            self.write_space();
31351        }
31352        if let Some(name) = &e.this {
31353            self.generate_expression(name)?;
31354            self.write_space();
31355        }
31356        if let Some(table) = &e.table {
31357            self.write_keyword("ON");
31358            self.write_space();
31359            self.generate_expression(table)?;
31360        }
31361        if !e.params.is_empty() {
31362            self.write("(");
31363            for (i, param) in e.params.iter().enumerate() {
31364                if i > 0 {
31365                    self.write(", ");
31366                }
31367                self.generate_expression(param)?;
31368            }
31369            self.write(")");
31370        }
31371        Ok(())
31372    }
31373
31374    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
31375        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
31376        if let Some(kind) = &e.kind {
31377            self.write(kind);
31378            self.write_space();
31379        }
31380        self.write_keyword("INDEX");
31381        if let Some(this) = &e.this {
31382            self.write_space();
31383            self.generate_expression(this)?;
31384        }
31385        if let Some(index_type) = &e.index_type {
31386            self.write_space();
31387            self.write_keyword("USING");
31388            self.write_space();
31389            self.generate_expression(index_type)?;
31390        }
31391        if !e.expressions.is_empty() {
31392            self.write(" (");
31393            for (i, expr) in e.expressions.iter().enumerate() {
31394                if i > 0 {
31395                    self.write(", ");
31396                }
31397                self.generate_expression(expr)?;
31398            }
31399            self.write(")");
31400        }
31401        for opt in &e.options {
31402            self.write_space();
31403            self.generate_expression(opt)?;
31404        }
31405        Ok(())
31406    }
31407
31408    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
31409        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
31410        if let Some(key_block_size) = &e.key_block_size {
31411            self.write_keyword("KEY_BLOCK_SIZE");
31412            self.write(" = ");
31413            self.generate_expression(key_block_size)?;
31414        } else if let Some(using) = &e.using {
31415            self.write_keyword("USING");
31416            self.write_space();
31417            self.generate_expression(using)?;
31418        } else if let Some(parser) = &e.parser {
31419            self.write_keyword("WITH PARSER");
31420            self.write_space();
31421            self.generate_expression(parser)?;
31422        } else if let Some(comment) = &e.comment {
31423            self.write_keyword("COMMENT");
31424            self.write_space();
31425            self.generate_expression(comment)?;
31426        } else if let Some(visible) = &e.visible {
31427            self.generate_expression(visible)?;
31428        } else if let Some(engine_attr) = &e.engine_attr {
31429            self.write_keyword("ENGINE_ATTRIBUTE");
31430            self.write(" = ");
31431            self.generate_expression(engine_attr)?;
31432        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
31433            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
31434            self.write(" = ");
31435            self.generate_expression(secondary_engine_attr)?;
31436        }
31437        Ok(())
31438    }
31439
31440    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
31441        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
31442        if let Some(using) = &e.using {
31443            self.write_keyword("USING");
31444            self.write_space();
31445            self.generate_expression(using)?;
31446        }
31447        if !e.columns.is_empty() {
31448            self.write("(");
31449            for (i, col) in e.columns.iter().enumerate() {
31450                if i > 0 {
31451                    self.write(", ");
31452                }
31453                self.generate_expression(col)?;
31454            }
31455            self.write(")");
31456        }
31457        if let Some(partition_by) = &e.partition_by {
31458            self.write_space();
31459            self.write_keyword("PARTITION BY");
31460            self.write_space();
31461            self.generate_expression(partition_by)?;
31462        }
31463        if let Some(where_) = &e.where_ {
31464            self.write_space();
31465            self.generate_expression(where_)?;
31466        }
31467        if let Some(include) = &e.include {
31468            self.write_space();
31469            self.write_keyword("INCLUDE");
31470            self.write(" (");
31471            self.generate_expression(include)?;
31472            self.write(")");
31473        }
31474        if let Some(with_storage) = &e.with_storage {
31475            self.write_space();
31476            self.write_keyword("WITH");
31477            self.write(" (");
31478            self.generate_expression(with_storage)?;
31479            self.write(")");
31480        }
31481        if let Some(tablespace) = &e.tablespace {
31482            self.write_space();
31483            self.write_keyword("USING INDEX TABLESPACE");
31484            self.write_space();
31485            self.generate_expression(tablespace)?;
31486        }
31487        Ok(())
31488    }
31489
31490    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
31491        // Python: this INDEX [FOR target] (expressions)
31492        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
31493        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
31494        if let Expression::Identifier(id) = &*e.this {
31495            self.write_keyword(&id.name);
31496        } else {
31497            self.generate_expression(&e.this)?;
31498        }
31499        self.write_space();
31500        self.write_keyword("INDEX");
31501        if let Some(target) = &e.target {
31502            self.write_space();
31503            self.write_keyword("FOR");
31504            self.write_space();
31505            if let Expression::Identifier(id) = &**target {
31506                self.write_keyword(&id.name);
31507            } else {
31508                self.generate_expression(target)?;
31509            }
31510        }
31511        // Always output parentheses (even if empty, e.g. USE INDEX ())
31512        self.write(" (");
31513        for (i, expr) in e.expressions.iter().enumerate() {
31514            if i > 0 {
31515                self.write(", ");
31516            }
31517            self.generate_expression(expr)?;
31518        }
31519        self.write(")");
31520        Ok(())
31521    }
31522
31523    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
31524        // INHERITS (table1, table2, ...)
31525        self.write_keyword("INHERITS");
31526        self.write(" (");
31527        for (i, expr) in e.expressions.iter().enumerate() {
31528            if i > 0 {
31529                self.write(", ");
31530            }
31531            self.generate_expression(expr)?;
31532        }
31533        self.write(")");
31534        Ok(())
31535    }
31536
31537    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
31538        // INPUT(model)
31539        self.write_keyword("INPUT");
31540        self.write("(");
31541        self.generate_expression(&e.this)?;
31542        self.write(")");
31543        Ok(())
31544    }
31545
31546    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
31547        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
31548        if let Some(input_format) = &e.input_format {
31549            self.write_keyword("INPUTFORMAT");
31550            self.write_space();
31551            self.generate_expression(input_format)?;
31552        }
31553        if let Some(output_format) = &e.output_format {
31554            if e.input_format.is_some() {
31555                self.write(" ");
31556            }
31557            self.write_keyword("OUTPUTFORMAT");
31558            self.write_space();
31559            self.generate_expression(output_format)?;
31560        }
31561        Ok(())
31562    }
31563
31564    fn generate_install(&mut self, e: &Install) -> Result<()> {
31565        // [FORCE] INSTALL extension [FROM source]
31566        if e.force.is_some() {
31567            self.write_keyword("FORCE");
31568            self.write_space();
31569        }
31570        self.write_keyword("INSTALL");
31571        self.write_space();
31572        self.generate_expression(&e.this)?;
31573        if let Some(from) = &e.from_ {
31574            self.write_space();
31575            self.write_keyword("FROM");
31576            self.write_space();
31577            self.generate_expression(from)?;
31578        }
31579        Ok(())
31580    }
31581
31582    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
31583        // INTERVAL 'expression' unit
31584        self.write_keyword("INTERVAL");
31585        self.write_space();
31586        // When a unit is specified and the expression is a number,
31587        self.generate_expression(&e.expression)?;
31588        if let Some(unit) = &e.unit {
31589            self.write_space();
31590            self.write(unit);
31591        }
31592        Ok(())
31593    }
31594
31595    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
31596        // unit TO unit (e.g., HOUR TO SECOND)
31597        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
31598        self.write_space();
31599        self.write_keyword("TO");
31600        self.write_space();
31601        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
31602        Ok(())
31603    }
31604
31605    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
31606        // INTO [TEMPORARY|UNLOGGED] table
31607        self.write_keyword("INTO");
31608        if e.temporary {
31609            self.write_keyword(" TEMPORARY");
31610        }
31611        if e.unlogged.is_some() {
31612            self.write_keyword(" UNLOGGED");
31613        }
31614        if let Some(this) = &e.this {
31615            self.write_space();
31616            self.generate_expression(this)?;
31617        }
31618        if !e.expressions.is_empty() {
31619            self.write(" (");
31620            for (i, expr) in e.expressions.iter().enumerate() {
31621                if i > 0 {
31622                    self.write(", ");
31623                }
31624                self.generate_expression(expr)?;
31625            }
31626            self.write(")");
31627        }
31628        Ok(())
31629    }
31630
31631    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
31632        // Python: this expression (e.g., _utf8 'string')
31633        self.generate_expression(&e.this)?;
31634        self.write_space();
31635        self.generate_expression(&e.expression)?;
31636        Ok(())
31637    }
31638
31639    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
31640        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
31641        self.write_keyword("WITH");
31642        if e.no.is_some() {
31643            self.write_keyword(" NO");
31644        }
31645        if e.concurrent.is_some() {
31646            self.write_keyword(" CONCURRENT");
31647        }
31648        self.write_keyword(" ISOLATED LOADING");
31649        if let Some(target) = &e.target {
31650            self.write_space();
31651            self.generate_expression(target)?;
31652        }
31653        Ok(())
31654    }
31655
31656    fn generate_json(&mut self, e: &JSON) -> Result<()> {
31657        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
31658        self.write_keyword("JSON");
31659        if let Some(this) = &e.this {
31660            self.write_space();
31661            self.generate_expression(this)?;
31662        }
31663        if let Some(with_) = &e.with_ {
31664            // Check if it's a truthy boolean
31665            if let Expression::Boolean(b) = with_.as_ref() {
31666                if b.value {
31667                    self.write_keyword(" WITH");
31668                } else {
31669                    self.write_keyword(" WITHOUT");
31670                }
31671            }
31672        }
31673        if e.unique {
31674            self.write_keyword(" UNIQUE KEYS");
31675        }
31676        Ok(())
31677    }
31678
31679    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
31680        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
31681        self.write_keyword("JSON_ARRAY");
31682        self.write("(");
31683        for (i, expr) in e.expressions.iter().enumerate() {
31684            if i > 0 {
31685                self.write(", ");
31686            }
31687            self.generate_expression(expr)?;
31688        }
31689        if let Some(null_handling) = &e.null_handling {
31690            self.write_space();
31691            self.generate_expression(null_handling)?;
31692        }
31693        if let Some(return_type) = &e.return_type {
31694            self.write_space();
31695            self.write_keyword("RETURNING");
31696            self.write_space();
31697            self.generate_expression(return_type)?;
31698        }
31699        if e.strict.is_some() {
31700            self.write_space();
31701            self.write_keyword("STRICT");
31702        }
31703        self.write(")");
31704        Ok(())
31705    }
31706
31707    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
31708        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
31709        self.write_keyword("JSON_ARRAYAGG");
31710        self.write("(");
31711        self.generate_expression(&e.this)?;
31712        if let Some(order) = &e.order {
31713            self.write_space();
31714            // Order is stored as an OrderBy expression
31715            if let Expression::OrderBy(ob) = order.as_ref() {
31716                self.write_keyword("ORDER BY");
31717                self.write_space();
31718                for (i, ord) in ob.expressions.iter().enumerate() {
31719                    if i > 0 {
31720                        self.write(", ");
31721                    }
31722                    self.generate_ordered(ord)?;
31723                }
31724            } else {
31725                // Fallback: generate the expression directly
31726                self.generate_expression(order)?;
31727            }
31728        }
31729        if let Some(null_handling) = &e.null_handling {
31730            self.write_space();
31731            self.generate_expression(null_handling)?;
31732        }
31733        if let Some(return_type) = &e.return_type {
31734            self.write_space();
31735            self.write_keyword("RETURNING");
31736            self.write_space();
31737            self.generate_expression(return_type)?;
31738        }
31739        if e.strict.is_some() {
31740            self.write_space();
31741            self.write_keyword("STRICT");
31742        }
31743        self.write(")");
31744        Ok(())
31745    }
31746
31747    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
31748        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
31749        self.write_keyword("JSON_OBJECTAGG");
31750        self.write("(");
31751        for (i, expr) in e.expressions.iter().enumerate() {
31752            if i > 0 {
31753                self.write(", ");
31754            }
31755            self.generate_expression(expr)?;
31756        }
31757        if let Some(null_handling) = &e.null_handling {
31758            self.write_space();
31759            self.generate_expression(null_handling)?;
31760        }
31761        if let Some(unique_keys) = &e.unique_keys {
31762            self.write_space();
31763            if let Expression::Boolean(b) = unique_keys.as_ref() {
31764                if b.value {
31765                    self.write_keyword("WITH UNIQUE KEYS");
31766                } else {
31767                    self.write_keyword("WITHOUT UNIQUE KEYS");
31768                }
31769            }
31770        }
31771        if let Some(return_type) = &e.return_type {
31772            self.write_space();
31773            self.write_keyword("RETURNING");
31774            self.write_space();
31775            self.generate_expression(return_type)?;
31776        }
31777        self.write(")");
31778        Ok(())
31779    }
31780
31781    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
31782        // JSON_ARRAY_APPEND(this, path, value, ...)
31783        self.write_keyword("JSON_ARRAY_APPEND");
31784        self.write("(");
31785        self.generate_expression(&e.this)?;
31786        for expr in &e.expressions {
31787            self.write(", ");
31788            self.generate_expression(expr)?;
31789        }
31790        self.write(")");
31791        Ok(())
31792    }
31793
31794    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
31795        // JSON_ARRAY_CONTAINS(this, expression)
31796        self.write_keyword("JSON_ARRAY_CONTAINS");
31797        self.write("(");
31798        self.generate_expression(&e.this)?;
31799        self.write(", ");
31800        self.generate_expression(&e.expression)?;
31801        self.write(")");
31802        Ok(())
31803    }
31804
31805    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
31806        // JSON_ARRAY_INSERT(this, path, value, ...)
31807        self.write_keyword("JSON_ARRAY_INSERT");
31808        self.write("(");
31809        self.generate_expression(&e.this)?;
31810        for expr in &e.expressions {
31811            self.write(", ");
31812            self.generate_expression(expr)?;
31813        }
31814        self.write(")");
31815        Ok(())
31816    }
31817
31818    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
31819        // JSONB_EXISTS(this, path)
31820        self.write_keyword("JSONB_EXISTS");
31821        self.write("(");
31822        self.generate_expression(&e.this)?;
31823        if let Some(path) = &e.path {
31824            self.write(", ");
31825            self.generate_expression(path)?;
31826        }
31827        self.write(")");
31828        Ok(())
31829    }
31830
31831    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
31832        // JSONB_EXTRACT_SCALAR(this, expression)
31833        self.write_keyword("JSONB_EXTRACT_SCALAR");
31834        self.write("(");
31835        self.generate_expression(&e.this)?;
31836        self.write(", ");
31837        self.generate_expression(&e.expression)?;
31838        self.write(")");
31839        Ok(())
31840    }
31841
31842    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
31843        // JSONB_OBJECT_AGG(this, expression)
31844        self.write_keyword("JSONB_OBJECT_AGG");
31845        self.write("(");
31846        self.generate_expression(&e.this)?;
31847        self.write(", ");
31848        self.generate_expression(&e.expression)?;
31849        self.write(")");
31850        Ok(())
31851    }
31852
31853    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
31854        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
31855        if let Some(nested_schema) = &e.nested_schema {
31856            self.write_keyword("NESTED");
31857            if let Some(path) = &e.path {
31858                self.write_space();
31859                self.write_keyword("PATH");
31860                self.write_space();
31861                self.generate_expression(path)?;
31862            }
31863            self.write_space();
31864            self.generate_expression(nested_schema)?;
31865        } else {
31866            if let Some(this) = &e.this {
31867                self.generate_expression(this)?;
31868            }
31869            if let Some(kind) = &e.kind {
31870                self.write_space();
31871                self.write(kind);
31872            }
31873            if e.format_json {
31874                self.write_space();
31875                self.write_keyword("FORMAT JSON");
31876            }
31877            if let Some(path) = &e.path {
31878                self.write_space();
31879                self.write_keyword("PATH");
31880                self.write_space();
31881                self.generate_expression(path)?;
31882            }
31883            if e.ordinality.is_some() {
31884                self.write_keyword(" FOR ORDINALITY");
31885            }
31886        }
31887        Ok(())
31888    }
31889
31890    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
31891        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
31892        self.write_keyword("JSON_EXISTS");
31893        self.write("(");
31894        self.generate_expression(&e.this)?;
31895        if let Some(path) = &e.path {
31896            self.write(", ");
31897            self.generate_expression(path)?;
31898        }
31899        if let Some(passing) = &e.passing {
31900            self.write_space();
31901            self.write_keyword("PASSING");
31902            self.write_space();
31903            self.generate_expression(passing)?;
31904        }
31905        if let Some(on_condition) = &e.on_condition {
31906            self.write_space();
31907            self.generate_expression(on_condition)?;
31908        }
31909        self.write(")");
31910        Ok(())
31911    }
31912
31913    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
31914        self.generate_expression(&e.this)?;
31915        self.write(".:");
31916        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
31917        // wrap the entire type string in double quotes.
31918        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
31919        if Self::data_type_has_nested_expressions(&e.to) {
31920            // Generate the data type to a temporary string buffer, then wrap in quotes
31921            let saved = std::mem::take(&mut self.output);
31922            self.generate_data_type(&e.to)?;
31923            let type_sql = std::mem::replace(&mut self.output, saved);
31924            self.write("\"");
31925            self.write(&type_sql);
31926            self.write("\"");
31927        } else {
31928            self.generate_data_type(&e.to)?;
31929        }
31930        Ok(())
31931    }
31932
31933    /// Check if a DataType has nested type expressions (sub-types).
31934    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
31935    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
31936        matches!(
31937            dt,
31938            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
31939        )
31940    }
31941
31942    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
31943        // JSON_EXTRACT_ARRAY(this, expression)
31944        self.write_keyword("JSON_EXTRACT_ARRAY");
31945        self.write("(");
31946        self.generate_expression(&e.this)?;
31947        if let Some(expr) = &e.expression {
31948            self.write(", ");
31949            self.generate_expression(expr)?;
31950        }
31951        self.write(")");
31952        Ok(())
31953    }
31954
31955    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
31956        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
31957        if let Some(option) = &e.option {
31958            self.generate_expression(option)?;
31959            self.write_space();
31960        }
31961        self.write_keyword("QUOTES");
31962        if e.scalar.is_some() {
31963            self.write_keyword(" SCALAR_ONLY");
31964        }
31965        Ok(())
31966    }
31967
31968    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
31969        // JSON_EXTRACT_SCALAR(this, expression)
31970        self.write_keyword("JSON_EXTRACT_SCALAR");
31971        self.write("(");
31972        self.generate_expression(&e.this)?;
31973        self.write(", ");
31974        self.generate_expression(&e.expression)?;
31975        self.write(")");
31976        Ok(())
31977    }
31978
31979    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
31980        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
31981        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
31982        // Otherwise output JSON_EXTRACT(this, expression)
31983        if e.variant_extract.is_some() {
31984            use crate::dialects::DialectType;
31985            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
31986                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
31987                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
31988                // bracket notation: c:["x-y"] instead of c:x-y
31989                self.generate_expression(&e.this)?;
31990                self.write(":");
31991                match e.expression.as_ref() {
31992                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
31993                        let Literal::String(s) = lit.as_ref() else {
31994                            unreachable!()
31995                        };
31996                        self.write_databricks_json_path(s);
31997                    }
31998                    _ => {
31999                        // Fallback: generate as-is (shouldn't happen in typical cases)
32000                        self.generate_expression(&e.expression)?;
32001                    }
32002                }
32003            } else {
32004                // Snowflake and others: use GET_PATH(col, 'path')
32005                self.write_keyword("GET_PATH");
32006                self.write("(");
32007                self.generate_expression(&e.this)?;
32008                self.write(", ");
32009                self.generate_expression(&e.expression)?;
32010                self.write(")");
32011            }
32012        } else {
32013            self.write_keyword("JSON_EXTRACT");
32014            self.write("(");
32015            self.generate_expression(&e.this)?;
32016            self.write(", ");
32017            self.generate_expression(&e.expression)?;
32018            for expr in &e.expressions {
32019                self.write(", ");
32020                self.generate_expression(expr)?;
32021            }
32022            self.write(")");
32023        }
32024        Ok(())
32025    }
32026
32027    /// Write a Databricks JSON colon-path, using bracket notation for keys
32028    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
32029    /// Safe identifier regex: ^[_a-zA-Z]\w*$
32030    fn write_databricks_json_path(&mut self, path: &str) {
32031        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
32032        // it was already formatted by the parser - output as-is
32033        if path.starts_with("[\"") || path.starts_with("['") {
32034            self.write(path);
32035            return;
32036        }
32037        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
32038        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
32039        // e.g., "x-y" -> ["x-y"]
32040        let mut first = true;
32041        for segment in path.split('.') {
32042            if !first {
32043                self.write(".");
32044            }
32045            first = false;
32046            // Check if there's a bracket subscript in this segment: "items[0]"
32047            if let Some(bracket_pos) = segment.find('[') {
32048                let key = &segment[..bracket_pos];
32049                let subscript = &segment[bracket_pos..];
32050                if key.is_empty() {
32051                    // Bracket notation at start of segment (e.g., already formatted)
32052                    self.write(segment);
32053                } else if Self::is_safe_json_path_key(key) {
32054                    self.write(key);
32055                    self.write(subscript);
32056                } else {
32057                    self.write("[\"");
32058                    self.write(key);
32059                    self.write("\"]");
32060                    self.write(subscript);
32061                }
32062            } else if Self::is_safe_json_path_key(segment) {
32063                self.write(segment);
32064            } else {
32065                self.write("[\"");
32066                self.write(segment);
32067                self.write("\"]");
32068            }
32069        }
32070    }
32071
32072    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
32073    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
32074    fn is_safe_json_path_key(key: &str) -> bool {
32075        if key.is_empty() {
32076            return false;
32077        }
32078        let mut chars = key.chars();
32079        let first = chars.next().unwrap();
32080        if first != '_' && !first.is_ascii_alphabetic() {
32081            return false;
32082        }
32083        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
32084    }
32085
32086    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
32087        // Output: {expr} FORMAT JSON
32088        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
32089        if let Some(this) = &e.this {
32090            self.generate_expression(this)?;
32091            self.write_space();
32092        }
32093        self.write_keyword("FORMAT JSON");
32094        Ok(())
32095    }
32096
32097    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
32098        // key: value (for JSON objects)
32099        self.generate_expression(&e.this)?;
32100        self.write(": ");
32101        self.generate_expression(&e.expression)?;
32102        Ok(())
32103    }
32104
32105    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
32106        // JSON_KEYS(this, expression, expressions...)
32107        self.write_keyword("JSON_KEYS");
32108        self.write("(");
32109        self.generate_expression(&e.this)?;
32110        if let Some(expr) = &e.expression {
32111            self.write(", ");
32112            self.generate_expression(expr)?;
32113        }
32114        for expr in &e.expressions {
32115            self.write(", ");
32116            self.generate_expression(expr)?;
32117        }
32118        self.write(")");
32119        Ok(())
32120    }
32121
32122    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
32123        // JSON_KEYS(this, expression)
32124        self.write_keyword("JSON_KEYS");
32125        self.write("(");
32126        self.generate_expression(&e.this)?;
32127        if let Some(expr) = &e.expression {
32128            self.write(", ");
32129            self.generate_expression(expr)?;
32130        }
32131        self.write(")");
32132        Ok(())
32133    }
32134
32135    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
32136        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
32137        // The path components are concatenated without spaces
32138        let mut path_str = String::new();
32139        for expr in &e.expressions {
32140            match expr {
32141                Expression::JSONPathRoot(_) => {
32142                    path_str.push('$');
32143                }
32144                Expression::JSONPathKey(k) => {
32145                    // .key or ."key" (quote if key has special characters)
32146                    if let Expression::Literal(lit) = k.this.as_ref() {
32147                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
32148                            path_str.push('.');
32149                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
32150                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
32151                            if needs_quoting {
32152                                path_str.push('"');
32153                                path_str.push_str(s);
32154                                path_str.push('"');
32155                            } else {
32156                                path_str.push_str(s);
32157                            }
32158                        }
32159                    }
32160                }
32161                Expression::JSONPathSubscript(s) => {
32162                    // [index]
32163                    if let Expression::Literal(lit) = s.this.as_ref() {
32164                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
32165                            path_str.push('[');
32166                            path_str.push_str(n);
32167                            path_str.push(']');
32168                        }
32169                    }
32170                }
32171                _ => {
32172                    // For other path parts, try to generate them
32173                    let mut temp_gen = Self::with_arc_config(self.config.clone());
32174                    temp_gen.generate_expression(expr)?;
32175                    path_str.push_str(&temp_gen.output);
32176                }
32177            }
32178        }
32179        // Output as quoted string
32180        self.write("'");
32181        self.write(&path_str);
32182        self.write("'");
32183        Ok(())
32184    }
32185
32186    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
32187        // JSON path filter: ?(predicate)
32188        self.write("?(");
32189        self.generate_expression(&e.this)?;
32190        self.write(")");
32191        Ok(())
32192    }
32193
32194    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
32195        // JSON path key: .key or ["key"]
32196        self.write(".");
32197        self.generate_expression(&e.this)?;
32198        Ok(())
32199    }
32200
32201    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
32202        // JSON path recursive descent: ..
32203        self.write("..");
32204        if let Some(this) = &e.this {
32205            self.generate_expression(this)?;
32206        }
32207        Ok(())
32208    }
32209
32210    fn generate_json_path_root(&mut self) -> Result<()> {
32211        // JSON path root: $
32212        self.write("$");
32213        Ok(())
32214    }
32215
32216    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
32217        // JSON path script: (expression)
32218        self.write("(");
32219        self.generate_expression(&e.this)?;
32220        self.write(")");
32221        Ok(())
32222    }
32223
32224    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
32225        // JSON path selector: *
32226        self.generate_expression(&e.this)?;
32227        Ok(())
32228    }
32229
32230    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
32231        // JSON path slice: [start:end:step]
32232        self.write("[");
32233        if let Some(start) = &e.start {
32234            self.generate_expression(start)?;
32235        }
32236        self.write(":");
32237        if let Some(end) = &e.end {
32238            self.generate_expression(end)?;
32239        }
32240        if let Some(step) = &e.step {
32241            self.write(":");
32242            self.generate_expression(step)?;
32243        }
32244        self.write("]");
32245        Ok(())
32246    }
32247
32248    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
32249        // JSON path subscript: [index] or [*]
32250        self.write("[");
32251        self.generate_expression(&e.this)?;
32252        self.write("]");
32253        Ok(())
32254    }
32255
32256    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
32257        // JSON path union: [key1, key2, ...]
32258        self.write("[");
32259        for (i, expr) in e.expressions.iter().enumerate() {
32260            if i > 0 {
32261                self.write(", ");
32262            }
32263            self.generate_expression(expr)?;
32264        }
32265        self.write("]");
32266        Ok(())
32267    }
32268
32269    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
32270        // JSON_REMOVE(this, path1, path2, ...)
32271        self.write_keyword("JSON_REMOVE");
32272        self.write("(");
32273        self.generate_expression(&e.this)?;
32274        for expr in &e.expressions {
32275            self.write(", ");
32276            self.generate_expression(expr)?;
32277        }
32278        self.write(")");
32279        Ok(())
32280    }
32281
32282    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
32283        // COLUMNS(col1 type, col2 type, ...)
32284        // When pretty printing and content is too wide, format with each column on a separate line
32285        self.write_keyword("COLUMNS");
32286        self.write("(");
32287
32288        if self.config.pretty && !e.expressions.is_empty() {
32289            // First, generate all expressions into strings to check width
32290            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
32291            for expr in &e.expressions {
32292                let mut temp_gen = Generator::with_arc_config(self.config.clone());
32293                temp_gen.generate_expression(expr)?;
32294                expr_strings.push(temp_gen.output);
32295            }
32296
32297            // Check if total width exceeds max_text_width
32298            if self.too_wide(&expr_strings) {
32299                // Pretty print: each column on its own line
32300                self.write_newline();
32301                self.indent_level += 1;
32302                for (i, expr_str) in expr_strings.iter().enumerate() {
32303                    if i > 0 {
32304                        self.write(",");
32305                        self.write_newline();
32306                    }
32307                    self.write_indent();
32308                    self.write(expr_str);
32309                }
32310                self.write_newline();
32311                self.indent_level -= 1;
32312                self.write_indent();
32313            } else {
32314                // Compact: all on one line
32315                for (i, expr_str) in expr_strings.iter().enumerate() {
32316                    if i > 0 {
32317                        self.write(", ");
32318                    }
32319                    self.write(expr_str);
32320                }
32321            }
32322        } else {
32323            // Non-pretty mode: compact format
32324            for (i, expr) in e.expressions.iter().enumerate() {
32325                if i > 0 {
32326                    self.write(", ");
32327                }
32328                self.generate_expression(expr)?;
32329            }
32330        }
32331        self.write(")");
32332        Ok(())
32333    }
32334
32335    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
32336        // JSON_SET(this, path, value, ...)
32337        self.write_keyword("JSON_SET");
32338        self.write("(");
32339        self.generate_expression(&e.this)?;
32340        for expr in &e.expressions {
32341            self.write(", ");
32342            self.generate_expression(expr)?;
32343        }
32344        self.write(")");
32345        Ok(())
32346    }
32347
32348    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
32349        // JSON_STRIP_NULLS(this, expression)
32350        self.write_keyword("JSON_STRIP_NULLS");
32351        self.write("(");
32352        self.generate_expression(&e.this)?;
32353        if let Some(expr) = &e.expression {
32354            self.write(", ");
32355            self.generate_expression(expr)?;
32356        }
32357        self.write(")");
32358        Ok(())
32359    }
32360
32361    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
32362        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
32363        self.write_keyword("JSON_TABLE");
32364        self.write("(");
32365        self.generate_expression(&e.this)?;
32366        if let Some(path) = &e.path {
32367            self.write(", ");
32368            self.generate_expression(path)?;
32369        }
32370        if let Some(error_handling) = &e.error_handling {
32371            self.write_space();
32372            self.generate_expression(error_handling)?;
32373        }
32374        if let Some(empty_handling) = &e.empty_handling {
32375            self.write_space();
32376            self.generate_expression(empty_handling)?;
32377        }
32378        if let Some(schema) = &e.schema {
32379            self.write_space();
32380            self.generate_expression(schema)?;
32381        }
32382        self.write(")");
32383        Ok(())
32384    }
32385
32386    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
32387        // JSON_TYPE(this)
32388        self.write_keyword("JSON_TYPE");
32389        self.write("(");
32390        self.generate_expression(&e.this)?;
32391        self.write(")");
32392        Ok(())
32393    }
32394
32395    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
32396        // JSON_VALUE(this, path RETURNING type ON condition)
32397        self.write_keyword("JSON_VALUE");
32398        self.write("(");
32399        self.generate_expression(&e.this)?;
32400        if let Some(path) = &e.path {
32401            self.write(", ");
32402            self.generate_expression(path)?;
32403        }
32404        if let Some(returning) = &e.returning {
32405            self.write_space();
32406            self.write_keyword("RETURNING");
32407            self.write_space();
32408            self.generate_expression(returning)?;
32409        }
32410        if let Some(on_condition) = &e.on_condition {
32411            self.write_space();
32412            self.generate_expression(on_condition)?;
32413        }
32414        self.write(")");
32415        Ok(())
32416    }
32417
32418    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
32419        // JSON_VALUE_ARRAY(this)
32420        self.write_keyword("JSON_VALUE_ARRAY");
32421        self.write("(");
32422        self.generate_expression(&e.this)?;
32423        self.write(")");
32424        Ok(())
32425    }
32426
32427    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
32428        // JAROWINKLER_SIMILARITY(str1, str2)
32429        self.write_keyword("JAROWINKLER_SIMILARITY");
32430        self.write("(");
32431        self.generate_expression(&e.this)?;
32432        self.write(", ");
32433        self.generate_expression(&e.expression)?;
32434        self.write(")");
32435        Ok(())
32436    }
32437
32438    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
32439        // Python: this(expressions)
32440        self.generate_expression(&e.this)?;
32441        self.write("(");
32442        for (i, expr) in e.expressions.iter().enumerate() {
32443            if i > 0 {
32444                self.write(", ");
32445            }
32446            self.generate_expression(expr)?;
32447        }
32448        self.write(")");
32449        Ok(())
32450    }
32451
32452    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
32453        // Python: {no}{local}{dual}{before}{after}JOURNAL
32454        if e.no.is_some() {
32455            self.write_keyword("NO ");
32456        }
32457        if let Some(local) = &e.local {
32458            self.generate_expression(local)?;
32459            self.write_space();
32460        }
32461        if e.dual.is_some() {
32462            self.write_keyword("DUAL ");
32463        }
32464        if e.before.is_some() {
32465            self.write_keyword("BEFORE ");
32466        }
32467        if e.after.is_some() {
32468            self.write_keyword("AFTER ");
32469        }
32470        self.write_keyword("JOURNAL");
32471        Ok(())
32472    }
32473
32474    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
32475        // LANGUAGE language_name
32476        self.write_keyword("LANGUAGE");
32477        self.write_space();
32478        self.generate_expression(&e.this)?;
32479        Ok(())
32480    }
32481
32482    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
32483        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
32484        if e.view.is_some() {
32485            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
32486            self.write_keyword("LATERAL VIEW");
32487            if e.outer.is_some() {
32488                self.write_space();
32489                self.write_keyword("OUTER");
32490            }
32491            self.write_space();
32492            self.generate_expression(&e.this)?;
32493            if let Some(alias) = &e.alias {
32494                self.write_space();
32495                self.write(alias);
32496            }
32497        } else {
32498            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
32499            self.write_keyword("LATERAL");
32500            self.write_space();
32501            self.generate_expression(&e.this)?;
32502            if e.ordinality.is_some() {
32503                self.write_space();
32504                self.write_keyword("WITH ORDINALITY");
32505            }
32506            if let Some(alias) = &e.alias {
32507                self.write_space();
32508                self.write_keyword("AS");
32509                self.write_space();
32510                self.write(alias);
32511                if !e.column_aliases.is_empty() {
32512                    self.write("(");
32513                    for (i, col) in e.column_aliases.iter().enumerate() {
32514                        if i > 0 {
32515                            self.write(", ");
32516                        }
32517                        self.write(col);
32518                    }
32519                    self.write(")");
32520                }
32521            }
32522        }
32523        Ok(())
32524    }
32525
32526    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
32527        // Python: LIKE this [options]
32528        self.write_keyword("LIKE");
32529        self.write_space();
32530        self.generate_expression(&e.this)?;
32531        for expr in &e.expressions {
32532            self.write_space();
32533            self.generate_expression(expr)?;
32534        }
32535        Ok(())
32536    }
32537
32538    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
32539        self.write_keyword("LIMIT");
32540        self.write_space();
32541        self.write_limit_expr(&e.this)?;
32542        if e.percent {
32543            self.write_space();
32544            self.write_keyword("PERCENT");
32545        }
32546        // Emit any comments that were captured from before the LIMIT keyword
32547        for comment in &e.comments {
32548            self.write(" ");
32549            self.write_formatted_comment(comment);
32550        }
32551        Ok(())
32552    }
32553
32554    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
32555        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
32556        if e.percent.is_some() {
32557            self.write_keyword(" PERCENT");
32558        }
32559        if e.rows.is_some() {
32560            self.write_keyword(" ROWS");
32561        }
32562        if e.with_ties.is_some() {
32563            self.write_keyword(" WITH TIES");
32564        } else if e.rows.is_some() {
32565            self.write_keyword(" ONLY");
32566        }
32567        Ok(())
32568    }
32569
32570    fn generate_list(&mut self, e: &List) -> Result<()> {
32571        use crate::dialects::DialectType;
32572        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
32573
32574        // Check if this is a subquery-based list (LIST(SELECT ...))
32575        if e.expressions.len() == 1 {
32576            if let Expression::Select(_) = &e.expressions[0] {
32577                self.write_keyword("LIST");
32578                self.write("(");
32579                self.generate_expression(&e.expressions[0])?;
32580                self.write(")");
32581                return Ok(());
32582            }
32583        }
32584
32585        // For Materialize, output as LIST[expr, expr, ...]
32586        if is_materialize {
32587            self.write_keyword("LIST");
32588            self.write("[");
32589            for (i, expr) in e.expressions.iter().enumerate() {
32590                if i > 0 {
32591                    self.write(", ");
32592                }
32593                self.generate_expression(expr)?;
32594            }
32595            self.write("]");
32596        } else {
32597            // For other dialects, output as LIST(expr, expr, ...)
32598            self.write_keyword("LIST");
32599            self.write("(");
32600            for (i, expr) in e.expressions.iter().enumerate() {
32601                if i > 0 {
32602                    self.write(", ");
32603                }
32604                self.generate_expression(expr)?;
32605            }
32606            self.write(")");
32607        }
32608        Ok(())
32609    }
32610
32611    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
32612        // Check if this is a subquery-based map (MAP(SELECT ...))
32613        if let Expression::Select(_) = &*e.this {
32614            self.write_keyword("MAP");
32615            self.write("(");
32616            self.generate_expression(&e.this)?;
32617            self.write(")");
32618            return Ok(());
32619        }
32620
32621        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
32622
32623        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
32624        self.write_keyword("MAP");
32625        if is_duckdb {
32626            self.write(" {");
32627        } else {
32628            self.write("[");
32629        }
32630        if let Expression::Struct(s) = &*e.this {
32631            for (i, (_, expr)) in s.fields.iter().enumerate() {
32632                if i > 0 {
32633                    self.write(", ");
32634                }
32635                if let Expression::PropertyEQ(op) = expr {
32636                    self.generate_expression(&op.left)?;
32637                    if is_duckdb {
32638                        self.write(": ");
32639                    } else {
32640                        self.write(" => ");
32641                    }
32642                    self.generate_expression(&op.right)?;
32643                } else {
32644                    self.generate_expression(expr)?;
32645                }
32646            }
32647        }
32648        if is_duckdb {
32649            self.write("}");
32650        } else {
32651            self.write("]");
32652        }
32653        Ok(())
32654    }
32655
32656    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
32657        // Python: LOCALTIME or LOCALTIME(precision)
32658        self.write_keyword("LOCALTIME");
32659        if let Some(precision) = &e.this {
32660            self.write("(");
32661            self.generate_expression(precision)?;
32662            self.write(")");
32663        }
32664        Ok(())
32665    }
32666
32667    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
32668        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
32669        self.write_keyword("LOCALTIMESTAMP");
32670        if let Some(precision) = &e.this {
32671            self.write("(");
32672            self.generate_expression(precision)?;
32673            self.write(")");
32674        }
32675        Ok(())
32676    }
32677
32678    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
32679        // LOCATION 'path'
32680        self.write_keyword("LOCATION");
32681        self.write_space();
32682        self.generate_expression(&e.this)?;
32683        Ok(())
32684    }
32685
32686    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
32687        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
32688        if e.update.is_some() {
32689            if e.key.is_some() {
32690                self.write_keyword("FOR NO KEY UPDATE");
32691            } else {
32692                self.write_keyword("FOR UPDATE");
32693            }
32694        } else {
32695            if e.key.is_some() {
32696                self.write_keyword("FOR KEY SHARE");
32697            } else {
32698                self.write_keyword("FOR SHARE");
32699            }
32700        }
32701        if !e.expressions.is_empty() {
32702            self.write_keyword(" OF ");
32703            for (i, expr) in e.expressions.iter().enumerate() {
32704                if i > 0 {
32705                    self.write(", ");
32706                }
32707                self.generate_expression(expr)?;
32708            }
32709        }
32710        // Handle wait option following Python sqlglot convention:
32711        // - Boolean(true) -> NOWAIT
32712        // - Boolean(false) -> SKIP LOCKED
32713        // - Literal (number) -> WAIT n
32714        if let Some(wait) = &e.wait {
32715            match wait.as_ref() {
32716                Expression::Boolean(b) => {
32717                    if b.value {
32718                        self.write_keyword(" NOWAIT");
32719                    } else {
32720                        self.write_keyword(" SKIP LOCKED");
32721                    }
32722                }
32723                _ => {
32724                    // It's a literal (number), output WAIT n
32725                    self.write_keyword(" WAIT ");
32726                    self.generate_expression(wait)?;
32727                }
32728            }
32729        }
32730        Ok(())
32731    }
32732
32733    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
32734        // LOCK property
32735        self.write_keyword("LOCK");
32736        self.write_space();
32737        self.generate_expression(&e.this)?;
32738        Ok(())
32739    }
32740
32741    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
32742        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
32743        self.write_keyword("LOCKING");
32744        self.write_space();
32745        self.write(&e.kind);
32746        if let Some(this) = &e.this {
32747            self.write_space();
32748            self.generate_expression(this)?;
32749        }
32750        if let Some(for_or_in) = &e.for_or_in {
32751            self.write_space();
32752            self.generate_expression(for_or_in)?;
32753        }
32754        if let Some(lock_type) = &e.lock_type {
32755            self.write_space();
32756            self.generate_expression(lock_type)?;
32757        }
32758        if e.override_.is_some() {
32759            self.write_keyword(" OVERRIDE");
32760        }
32761        Ok(())
32762    }
32763
32764    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
32765        // this expression
32766        self.generate_expression(&e.this)?;
32767        self.write_space();
32768        self.generate_expression(&e.expression)?;
32769        Ok(())
32770    }
32771
32772    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
32773        // [NO] LOG
32774        if e.no.is_some() {
32775            self.write_keyword("NO ");
32776        }
32777        self.write_keyword("LOG");
32778        Ok(())
32779    }
32780
32781    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
32782        // MD5(this, expressions...)
32783        self.write_keyword("MD5");
32784        self.write("(");
32785        self.generate_expression(&e.this)?;
32786        for expr in &e.expressions {
32787            self.write(", ");
32788            self.generate_expression(expr)?;
32789        }
32790        self.write(")");
32791        Ok(())
32792    }
32793
32794    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
32795        // ML.FORECAST(model, [params])
32796        self.write_keyword("ML.FORECAST");
32797        self.write("(");
32798        self.generate_expression(&e.this)?;
32799        if let Some(expression) = &e.expression {
32800            self.write(", ");
32801            self.generate_expression(expression)?;
32802        }
32803        if let Some(params) = &e.params_struct {
32804            self.write(", ");
32805            self.generate_expression(params)?;
32806        }
32807        self.write(")");
32808        Ok(())
32809    }
32810
32811    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
32812        // ML.TRANSLATE(model, input, [params])
32813        self.write_keyword("ML.TRANSLATE");
32814        self.write("(");
32815        self.generate_expression(&e.this)?;
32816        self.write(", ");
32817        self.generate_expression(&e.expression)?;
32818        if let Some(params) = &e.params_struct {
32819            self.write(", ");
32820            self.generate_expression(params)?;
32821        }
32822        self.write(")");
32823        Ok(())
32824    }
32825
32826    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
32827        // MAKE_INTERVAL(years => x, months => y, ...)
32828        self.write_keyword("MAKE_INTERVAL");
32829        self.write("(");
32830        let mut first = true;
32831        if let Some(year) = &e.year {
32832            self.write("years => ");
32833            self.generate_expression(year)?;
32834            first = false;
32835        }
32836        if let Some(month) = &e.month {
32837            if !first {
32838                self.write(", ");
32839            }
32840            self.write("months => ");
32841            self.generate_expression(month)?;
32842            first = false;
32843        }
32844        if let Some(week) = &e.week {
32845            if !first {
32846                self.write(", ");
32847            }
32848            self.write("weeks => ");
32849            self.generate_expression(week)?;
32850            first = false;
32851        }
32852        if let Some(day) = &e.day {
32853            if !first {
32854                self.write(", ");
32855            }
32856            self.write("days => ");
32857            self.generate_expression(day)?;
32858            first = false;
32859        }
32860        if let Some(hour) = &e.hour {
32861            if !first {
32862                self.write(", ");
32863            }
32864            self.write("hours => ");
32865            self.generate_expression(hour)?;
32866            first = false;
32867        }
32868        if let Some(minute) = &e.minute {
32869            if !first {
32870                self.write(", ");
32871            }
32872            self.write("mins => ");
32873            self.generate_expression(minute)?;
32874            first = false;
32875        }
32876        if let Some(second) = &e.second {
32877            if !first {
32878                self.write(", ");
32879            }
32880            self.write("secs => ");
32881            self.generate_expression(second)?;
32882        }
32883        self.write(")");
32884        Ok(())
32885    }
32886
32887    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
32888        // MANHATTAN_DISTANCE(vector1, vector2)
32889        self.write_keyword("MANHATTAN_DISTANCE");
32890        self.write("(");
32891        self.generate_expression(&e.this)?;
32892        self.write(", ");
32893        self.generate_expression(&e.expression)?;
32894        self.write(")");
32895        Ok(())
32896    }
32897
32898    fn generate_map(&mut self, e: &Map) -> Result<()> {
32899        // MAP(key1, value1, key2, value2, ...)
32900        self.write_keyword("MAP");
32901        self.write("(");
32902        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
32903            if i > 0 {
32904                self.write(", ");
32905            }
32906            self.generate_expression(key)?;
32907            self.write(", ");
32908            self.generate_expression(value)?;
32909        }
32910        self.write(")");
32911        Ok(())
32912    }
32913
32914    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
32915        // MAP_CAT(map1, map2)
32916        self.write_keyword("MAP_CAT");
32917        self.write("(");
32918        self.generate_expression(&e.this)?;
32919        self.write(", ");
32920        self.generate_expression(&e.expression)?;
32921        self.write(")");
32922        Ok(())
32923    }
32924
32925    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
32926        // MAP_DELETE(map, key1, key2, ...)
32927        self.write_keyword("MAP_DELETE");
32928        self.write("(");
32929        self.generate_expression(&e.this)?;
32930        for expr in &e.expressions {
32931            self.write(", ");
32932            self.generate_expression(expr)?;
32933        }
32934        self.write(")");
32935        Ok(())
32936    }
32937
32938    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
32939        // MAP_INSERT(map, key, value, [update_flag])
32940        self.write_keyword("MAP_INSERT");
32941        self.write("(");
32942        self.generate_expression(&e.this)?;
32943        if let Some(key) = &e.key {
32944            self.write(", ");
32945            self.generate_expression(key)?;
32946        }
32947        if let Some(value) = &e.value {
32948            self.write(", ");
32949            self.generate_expression(value)?;
32950        }
32951        if let Some(update_flag) = &e.update_flag {
32952            self.write(", ");
32953            self.generate_expression(update_flag)?;
32954        }
32955        self.write(")");
32956        Ok(())
32957    }
32958
32959    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
32960        // MAP_PICK(map, key1, key2, ...)
32961        self.write_keyword("MAP_PICK");
32962        self.write("(");
32963        self.generate_expression(&e.this)?;
32964        for expr in &e.expressions {
32965            self.write(", ");
32966            self.generate_expression(expr)?;
32967        }
32968        self.write(")");
32969        Ok(())
32970    }
32971
32972    fn generate_masking_policy_column_constraint(
32973        &mut self,
32974        e: &MaskingPolicyColumnConstraint,
32975    ) -> Result<()> {
32976        // Python: MASKING POLICY name [USING (cols)]
32977        self.write_keyword("MASKING POLICY");
32978        self.write_space();
32979        self.generate_expression(&e.this)?;
32980        if !e.expressions.is_empty() {
32981            self.write_keyword(" USING");
32982            self.write(" (");
32983            for (i, expr) in e.expressions.iter().enumerate() {
32984                if i > 0 {
32985                    self.write(", ");
32986                }
32987                self.generate_expression(expr)?;
32988            }
32989            self.write(")");
32990        }
32991        Ok(())
32992    }
32993
32994    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
32995        if matches!(
32996            self.config.dialect,
32997            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
32998        ) {
32999            if e.expressions.len() > 1 {
33000                self.write("(");
33001            }
33002            for (i, expr) in e.expressions.iter().enumerate() {
33003                if i > 0 {
33004                    self.write_keyword(" OR ");
33005                }
33006                self.generate_expression(expr)?;
33007                self.write_space();
33008                self.write("@@");
33009                self.write_space();
33010                self.generate_expression(&e.this)?;
33011            }
33012            if e.expressions.len() > 1 {
33013                self.write(")");
33014            }
33015            return Ok(());
33016        }
33017
33018        // MATCH(columns) AGAINST(expr [modifier])
33019        self.write_keyword("MATCH");
33020        self.write("(");
33021        for (i, expr) in e.expressions.iter().enumerate() {
33022            if i > 0 {
33023                self.write(", ");
33024            }
33025            self.generate_expression(expr)?;
33026        }
33027        self.write(")");
33028        self.write_keyword(" AGAINST");
33029        self.write("(");
33030        self.generate_expression(&e.this)?;
33031        if let Some(modifier) = &e.modifier {
33032            self.write_space();
33033            self.generate_expression(modifier)?;
33034        }
33035        self.write(")");
33036        Ok(())
33037    }
33038
33039    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
33040        // Python: [window_frame] this
33041        if let Some(window_frame) = &e.window_frame {
33042            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
33043            self.write_space();
33044        }
33045        self.generate_expression(&e.this)?;
33046        Ok(())
33047    }
33048
33049    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
33050        // MATERIALIZED [this]
33051        self.write_keyword("MATERIALIZED");
33052        if let Some(this) = &e.this {
33053            self.write_space();
33054            self.generate_expression(this)?;
33055        }
33056        Ok(())
33057    }
33058
33059    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
33060        // MERGE INTO target USING source ON condition WHEN ...
33061        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
33062        if let Some(with_) = &e.with_ {
33063            if let Expression::With(with_clause) = with_.as_ref() {
33064                self.generate_with(with_clause)?;
33065                self.write_space();
33066            } else {
33067                self.generate_expression(with_)?;
33068                self.write_space();
33069            }
33070        }
33071        self.write_keyword("MERGE INTO");
33072        self.write_space();
33073        if matches!(self.config.dialect, Some(crate::DialectType::Oracle)) {
33074            if let Expression::Alias(alias) = e.this.as_ref() {
33075                self.generate_expression(&alias.this)?;
33076                self.write_space();
33077                self.generate_identifier(&alias.alias)?;
33078            } else {
33079                self.generate_expression(&e.this)?;
33080            }
33081        } else {
33082            self.generate_expression(&e.this)?;
33083        }
33084
33085        // USING clause - newline before in pretty mode
33086        if self.config.pretty {
33087            self.write_newline();
33088            self.write_indent();
33089        } else {
33090            self.write_space();
33091        }
33092        self.write_keyword("USING");
33093        self.write_space();
33094        self.generate_expression(&e.using)?;
33095
33096        // ON clause - newline before in pretty mode
33097        if let Some(on) = &e.on {
33098            if self.config.pretty {
33099                self.write_newline();
33100                self.write_indent();
33101            } else {
33102                self.write_space();
33103            }
33104            self.write_keyword("ON");
33105            self.write_space();
33106            self.generate_expression(on)?;
33107        }
33108        // DuckDB USING (key_columns) clause
33109        if let Some(using_cond) = &e.using_cond {
33110            self.write_space();
33111            self.write_keyword("USING");
33112            self.write_space();
33113            self.write("(");
33114            // using_cond is a Tuple containing the column identifiers
33115            if let Expression::Tuple(tuple) = using_cond.as_ref() {
33116                for (i, col) in tuple.expressions.iter().enumerate() {
33117                    if i > 0 {
33118                        self.write(", ");
33119                    }
33120                    self.generate_expression(col)?;
33121                }
33122            } else {
33123                self.generate_expression(using_cond)?;
33124            }
33125            self.write(")");
33126        }
33127        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
33128        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
33129        if matches!(
33130            self.config.dialect,
33131            Some(crate::DialectType::PostgreSQL)
33132                | Some(crate::DialectType::Redshift)
33133                | Some(crate::DialectType::Trino)
33134                | Some(crate::DialectType::Presto)
33135                | Some(crate::DialectType::Athena)
33136        ) {
33137            let mut names = Vec::new();
33138            match e.this.as_ref() {
33139                Expression::Alias(a) => {
33140                    // e.g., "x AS z" -> strip both "x" and "z"
33141                    if let Expression::Table(t) = &a.this {
33142                        names.push(t.name.name.clone());
33143                    } else if let Expression::Identifier(id) = &a.this {
33144                        names.push(id.name.clone());
33145                    }
33146                    names.push(a.alias.name.clone());
33147                }
33148                Expression::Table(t) => {
33149                    names.push(t.name.name.clone());
33150                }
33151                Expression::Identifier(id) => {
33152                    names.push(id.name.clone());
33153                }
33154                _ => {}
33155            }
33156            self.merge_strip_qualifiers = names;
33157        }
33158
33159        // WHEN clauses - newline before each in pretty mode
33160        if let Some(whens) = &e.whens {
33161            if self.config.pretty {
33162                self.write_newline();
33163                self.write_indent();
33164            } else {
33165                self.write_space();
33166            }
33167            self.generate_expression(whens)?;
33168        }
33169
33170        // Restore merge_strip_qualifiers
33171        self.merge_strip_qualifiers = saved_merge_strip;
33172
33173        // OUTPUT/RETURNING clause - newline before in pretty mode
33174        if let Some(returning) = &e.returning {
33175            if self.config.pretty {
33176                self.write_newline();
33177                self.write_indent();
33178            } else {
33179                self.write_space();
33180            }
33181            self.generate_expression(returning)?;
33182        }
33183        Ok(())
33184    }
33185
33186    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
33187        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
33188        if e.no.is_some() {
33189            self.write_keyword("NO MERGEBLOCKRATIO");
33190        } else if e.default.is_some() {
33191            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
33192        } else {
33193            self.write_keyword("MERGEBLOCKRATIO");
33194            self.write("=");
33195            if let Some(this) = &e.this {
33196                self.generate_expression(this)?;
33197            }
33198            if e.percent.is_some() {
33199                self.write_keyword(" PERCENT");
33200            }
33201        }
33202        Ok(())
33203    }
33204
33205    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
33206        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
33207        self.write_keyword("TTL");
33208        let pretty_clickhouse = self.config.pretty
33209            && matches!(
33210                self.config.dialect,
33211                Some(crate::dialects::DialectType::ClickHouse)
33212            );
33213
33214        if pretty_clickhouse {
33215            self.write_newline();
33216            self.indent_level += 1;
33217            for (i, expr) in e.expressions.iter().enumerate() {
33218                if i > 0 {
33219                    self.write(",");
33220                    self.write_newline();
33221                }
33222                self.write_indent();
33223                self.generate_expression(expr)?;
33224            }
33225            self.indent_level -= 1;
33226        } else {
33227            self.write_space();
33228            for (i, expr) in e.expressions.iter().enumerate() {
33229                if i > 0 {
33230                    self.write(", ");
33231                }
33232                self.generate_expression(expr)?;
33233            }
33234        }
33235
33236        if let Some(where_) = &e.where_ {
33237            if pretty_clickhouse {
33238                self.write_newline();
33239                if let Expression::Where(w) = where_.as_ref() {
33240                    self.write_indent();
33241                    self.write_keyword("WHERE");
33242                    self.write_newline();
33243                    self.indent_level += 1;
33244                    self.write_indent();
33245                    self.generate_expression(&w.this)?;
33246                    self.indent_level -= 1;
33247                } else {
33248                    self.write_indent();
33249                    self.generate_expression(where_)?;
33250                }
33251            } else {
33252                self.write_space();
33253                self.generate_expression(where_)?;
33254            }
33255        }
33256        if let Some(group) = &e.group {
33257            if pretty_clickhouse {
33258                self.write_newline();
33259                if let Expression::Group(g) = group.as_ref() {
33260                    self.write_indent();
33261                    self.write_keyword("GROUP BY");
33262                    self.write_newline();
33263                    self.indent_level += 1;
33264                    for (i, expr) in g.expressions.iter().enumerate() {
33265                        if i > 0 {
33266                            self.write(",");
33267                            self.write_newline();
33268                        }
33269                        self.write_indent();
33270                        self.generate_expression(expr)?;
33271                    }
33272                    self.indent_level -= 1;
33273                } else {
33274                    self.write_indent();
33275                    self.generate_expression(group)?;
33276                }
33277            } else {
33278                self.write_space();
33279                self.generate_expression(group)?;
33280            }
33281        }
33282        if let Some(aggregates) = &e.aggregates {
33283            if pretty_clickhouse {
33284                self.write_newline();
33285                self.write_indent();
33286                self.write_keyword("SET");
33287                self.write_newline();
33288                self.indent_level += 1;
33289                if let Expression::Tuple(t) = aggregates.as_ref() {
33290                    for (i, agg) in t.expressions.iter().enumerate() {
33291                        if i > 0 {
33292                            self.write(",");
33293                            self.write_newline();
33294                        }
33295                        self.write_indent();
33296                        self.generate_expression(agg)?;
33297                    }
33298                } else {
33299                    self.write_indent();
33300                    self.generate_expression(aggregates)?;
33301                }
33302                self.indent_level -= 1;
33303            } else {
33304                self.write_space();
33305                self.write_keyword("SET");
33306                self.write_space();
33307                if let Expression::Tuple(t) = aggregates.as_ref() {
33308                    for (i, agg) in t.expressions.iter().enumerate() {
33309                        if i > 0 {
33310                            self.write(", ");
33311                        }
33312                        self.generate_expression(agg)?;
33313                    }
33314                } else {
33315                    self.generate_expression(aggregates)?;
33316                }
33317            }
33318        }
33319        Ok(())
33320    }
33321
33322    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
33323        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
33324        self.generate_expression(&e.this)?;
33325        if e.delete.is_some() {
33326            self.write_keyword(" DELETE");
33327        }
33328        if let Some(recompress) = &e.recompress {
33329            self.write_keyword(" RECOMPRESS ");
33330            self.generate_expression(recompress)?;
33331        }
33332        if let Some(to_disk) = &e.to_disk {
33333            self.write_keyword(" TO DISK ");
33334            self.generate_expression(to_disk)?;
33335        }
33336        if let Some(to_volume) = &e.to_volume {
33337            self.write_keyword(" TO VOLUME ");
33338            self.generate_expression(to_volume)?;
33339        }
33340        Ok(())
33341    }
33342
33343    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
33344        // MINHASH(this, expressions...)
33345        self.write_keyword("MINHASH");
33346        self.write("(");
33347        self.generate_expression(&e.this)?;
33348        for expr in &e.expressions {
33349            self.write(", ");
33350            self.generate_expression(expr)?;
33351        }
33352        self.write(")");
33353        Ok(())
33354    }
33355
33356    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
33357        // model!attribute - Snowflake syntax
33358        self.generate_expression(&e.this)?;
33359        self.write("!");
33360        self.generate_expression(&e.expression)?;
33361        Ok(())
33362    }
33363
33364    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
33365        // MONTHNAME(this)
33366        self.write_keyword("MONTHNAME");
33367        self.write("(");
33368        self.generate_expression(&e.this)?;
33369        self.write(")");
33370        Ok(())
33371    }
33372
33373    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
33374        // Output leading comments
33375        for comment in &e.leading_comments {
33376            self.write_formatted_comment(comment);
33377            if self.config.pretty {
33378                self.write_newline();
33379                self.write_indent();
33380            } else {
33381                self.write_space();
33382            }
33383        }
33384        // Python: INSERT [OVERWRITE] kind expressions source
33385        self.write_keyword("INSERT");
33386        if e.overwrite {
33387            self.write_space();
33388            self.write_keyword("OVERWRITE");
33389        }
33390        self.write_space();
33391        self.write(&e.kind);
33392        if self.config.pretty {
33393            self.indent_level += 1;
33394            for expr in &e.expressions {
33395                self.write_newline();
33396                self.write_indent();
33397                self.generate_expression(expr)?;
33398            }
33399            self.indent_level -= 1;
33400        } else {
33401            for expr in &e.expressions {
33402                self.write_space();
33403                self.generate_expression(expr)?;
33404            }
33405        }
33406        if let Some(source) = &e.source {
33407            if self.config.pretty {
33408                self.write_newline();
33409                self.write_indent();
33410            } else {
33411                self.write_space();
33412            }
33413            self.generate_expression(source)?;
33414        }
33415        Ok(())
33416    }
33417
33418    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
33419        // Python: NEXT VALUE FOR this [OVER (order)]
33420        self.write_keyword("NEXT VALUE FOR");
33421        self.write_space();
33422        self.generate_expression(&e.this)?;
33423        if let Some(order) = &e.order {
33424            self.write_space();
33425            self.write_keyword("OVER");
33426            self.write(" (");
33427            self.generate_expression(order)?;
33428            self.write(")");
33429        }
33430        Ok(())
33431    }
33432
33433    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
33434        // NORMAL(mean, stddev, gen)
33435        self.write_keyword("NORMAL");
33436        self.write("(");
33437        self.generate_expression(&e.this)?;
33438        if let Some(stddev) = &e.stddev {
33439            self.write(", ");
33440            self.generate_expression(stddev)?;
33441        }
33442        if let Some(gen) = &e.gen {
33443            self.write(", ");
33444            self.generate_expression(gen)?;
33445        }
33446        self.write(")");
33447        Ok(())
33448    }
33449
33450    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
33451        // NORMALIZE(this, form) or CASEFOLD version
33452        if e.is_casefold.is_some() {
33453            self.write_keyword("NORMALIZE_AND_CASEFOLD");
33454        } else {
33455            self.write_keyword("NORMALIZE");
33456        }
33457        self.write("(");
33458        self.generate_expression(&e.this)?;
33459        if let Some(form) = &e.form {
33460            self.write(", ");
33461            self.generate_expression(form)?;
33462        }
33463        self.write(")");
33464        Ok(())
33465    }
33466
33467    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
33468        // Python: [NOT ]NULL
33469        if e.allow_null.is_none() {
33470            self.write_keyword("NOT ");
33471        }
33472        self.write_keyword("NULL");
33473        Ok(())
33474    }
33475
33476    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
33477        // NULLIF(this, expression)
33478        self.write_keyword("NULLIF");
33479        self.write("(");
33480        self.generate_expression(&e.this)?;
33481        self.write(", ");
33482        self.generate_expression(&e.expression)?;
33483        self.write(")");
33484        Ok(())
33485    }
33486
33487    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
33488        // FORMAT(this, format, culture)
33489        self.write_keyword("FORMAT");
33490        self.write("(");
33491        self.generate_expression(&e.this)?;
33492        self.write(", '");
33493        self.write(&e.format);
33494        self.write("'");
33495        if let Some(culture) = &e.culture {
33496            self.write(", ");
33497            self.generate_expression(culture)?;
33498        }
33499        self.write(")");
33500        Ok(())
33501    }
33502
33503    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
33504        // OBJECT_AGG(key, value)
33505        self.write_keyword("OBJECT_AGG");
33506        self.write("(");
33507        self.generate_expression(&e.this)?;
33508        self.write(", ");
33509        self.generate_expression(&e.expression)?;
33510        self.write(")");
33511        Ok(())
33512    }
33513
33514    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
33515        // Python: Just returns the name
33516        self.generate_expression(&e.this)?;
33517        Ok(())
33518    }
33519
33520    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
33521        // OBJECT_INSERT(obj, key, value, [update_flag])
33522        self.write_keyword("OBJECT_INSERT");
33523        self.write("(");
33524        self.generate_expression(&e.this)?;
33525        if let Some(key) = &e.key {
33526            self.write(", ");
33527            self.generate_expression(key)?;
33528        }
33529        if let Some(value) = &e.value {
33530            self.write(", ");
33531            self.generate_expression(value)?;
33532        }
33533        if let Some(update_flag) = &e.update_flag {
33534            self.write(", ");
33535            self.generate_expression(update_flag)?;
33536        }
33537        self.write(")");
33538        Ok(())
33539    }
33540
33541    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
33542        // OFFSET value [ROW|ROWS]
33543        self.write_keyword("OFFSET");
33544        self.write_space();
33545        self.generate_expression(&e.this)?;
33546        // Output ROWS keyword only for TSQL/Oracle targets
33547        if e.rows == Some(true)
33548            && matches!(
33549                self.config.dialect,
33550                Some(crate::dialects::DialectType::TSQL)
33551                    | Some(crate::dialects::DialectType::Oracle)
33552            )
33553        {
33554            self.write_space();
33555            self.write_keyword("ROWS");
33556        }
33557        Ok(())
33558    }
33559
33560    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
33561        // QUALIFY condition (Snowflake/BigQuery)
33562        self.write_keyword("QUALIFY");
33563        self.write_space();
33564        self.generate_expression(&e.this)?;
33565        Ok(())
33566    }
33567
33568    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
33569        // ON CLUSTER cluster_name
33570        self.write_keyword("ON CLUSTER");
33571        self.write_space();
33572        self.generate_expression(&e.this)?;
33573        Ok(())
33574    }
33575
33576    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
33577        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
33578        self.write_keyword("ON COMMIT");
33579        if e.delete.is_some() {
33580            self.write_keyword(" DELETE ROWS");
33581        } else {
33582            self.write_keyword(" PRESERVE ROWS");
33583        }
33584        Ok(())
33585    }
33586
33587    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
33588        // Python: error/empty/null handling
33589        if let Some(empty) = &e.empty {
33590            self.generate_expression(empty)?;
33591            self.write_keyword(" ON EMPTY");
33592        }
33593        if let Some(error) = &e.error {
33594            if e.empty.is_some() {
33595                self.write_space();
33596            }
33597            self.generate_expression(error)?;
33598            self.write_keyword(" ON ERROR");
33599        }
33600        if let Some(null) = &e.null {
33601            if e.empty.is_some() || e.error.is_some() {
33602                self.write_space();
33603            }
33604            self.generate_expression(null)?;
33605            self.write_keyword(" ON NULL");
33606        }
33607        Ok(())
33608    }
33609
33610    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
33611        // Materialize doesn't support ON CONFLICT - skip entirely
33612        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
33613            return Ok(());
33614        }
33615        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
33616        if e.duplicate.is_some() {
33617            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
33618            self.write_keyword("ON DUPLICATE KEY UPDATE");
33619            for (i, expr) in e.expressions.iter().enumerate() {
33620                if i > 0 {
33621                    self.write(",");
33622                }
33623                self.write_space();
33624                self.generate_expression(expr)?;
33625            }
33626            return Ok(());
33627        } else {
33628            self.write_keyword("ON CONFLICT");
33629        }
33630        if let Some(constraint) = &e.constraint {
33631            self.write_keyword(" ON CONSTRAINT ");
33632            self.generate_expression(constraint)?;
33633        }
33634        if let Some(conflict_keys) = &e.conflict_keys {
33635            // conflict_keys can be a Tuple containing expressions
33636            if let Expression::Tuple(t) = conflict_keys.as_ref() {
33637                self.write("(");
33638                for (i, expr) in t.expressions.iter().enumerate() {
33639                    if i > 0 {
33640                        self.write(", ");
33641                    }
33642                    self.generate_expression(expr)?;
33643                }
33644                self.write(")");
33645            } else {
33646                self.write("(");
33647                self.generate_expression(conflict_keys)?;
33648                self.write(")");
33649            }
33650        }
33651        if let Some(index_predicate) = &e.index_predicate {
33652            self.write_keyword(" WHERE ");
33653            self.generate_expression(index_predicate)?;
33654        }
33655        if let Some(action) = &e.action {
33656            // Check if action is "NOTHING" or an UPDATE set
33657            if let Expression::Identifier(id) = action.as_ref() {
33658                if id.name.eq_ignore_ascii_case("NOTHING") {
33659                    self.write_keyword(" DO NOTHING");
33660                } else {
33661                    self.write_keyword(" DO ");
33662                    self.generate_expression(action)?;
33663                }
33664            } else if let Expression::Tuple(t) = action.as_ref() {
33665                // DO UPDATE SET col1 = val1, col2 = val2
33666                self.write_keyword(" DO UPDATE SET ");
33667                for (i, expr) in t.expressions.iter().enumerate() {
33668                    if i > 0 {
33669                        self.write(", ");
33670                    }
33671                    self.generate_expression(expr)?;
33672                }
33673            } else {
33674                self.write_keyword(" DO ");
33675                self.generate_expression(action)?;
33676            }
33677        }
33678        // WHERE clause for the UPDATE action
33679        if let Some(where_) = &e.where_ {
33680            self.write_keyword(" WHERE ");
33681            self.generate_expression(where_)?;
33682        }
33683        Ok(())
33684    }
33685
33686    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
33687        // ON property_value
33688        self.write_keyword("ON");
33689        self.write_space();
33690        self.generate_expression(&e.this)?;
33691        Ok(())
33692    }
33693
33694    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
33695        // Python: this expression (e.g., column opclass)
33696        self.generate_expression(&e.this)?;
33697        self.write_space();
33698        self.generate_expression(&e.expression)?;
33699        Ok(())
33700    }
33701
33702    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
33703        // Python: OPENJSON(this[, path]) [WITH (columns)]
33704        self.write_keyword("OPENJSON");
33705        self.write("(");
33706        self.generate_expression(&e.this)?;
33707        if let Some(path) = &e.path {
33708            self.write(", ");
33709            self.generate_expression(path)?;
33710        }
33711        self.write(")");
33712        if !e.expressions.is_empty() {
33713            self.write_keyword(" WITH");
33714            if self.config.pretty {
33715                self.write(" (\n");
33716                self.indent_level += 2;
33717                for (i, expr) in e.expressions.iter().enumerate() {
33718                    if i > 0 {
33719                        self.write(",\n");
33720                    }
33721                    self.write_indent();
33722                    self.generate_expression(expr)?;
33723                }
33724                self.write("\n");
33725                self.indent_level -= 2;
33726                self.write(")");
33727            } else {
33728                self.write(" (");
33729                for (i, expr) in e.expressions.iter().enumerate() {
33730                    if i > 0 {
33731                        self.write(", ");
33732                    }
33733                    self.generate_expression(expr)?;
33734                }
33735                self.write(")");
33736            }
33737        }
33738        Ok(())
33739    }
33740
33741    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
33742        // Python: this kind [path] [AS JSON]
33743        self.generate_expression(&e.this)?;
33744        self.write_space();
33745        // Use parsed data_type if available, otherwise fall back to kind string
33746        if let Some(ref dt) = e.data_type {
33747            self.generate_data_type(dt)?;
33748        } else if !e.kind.is_empty() {
33749            self.write(&e.kind);
33750        }
33751        if let Some(path) = &e.path {
33752            self.write_space();
33753            self.generate_expression(path)?;
33754        }
33755        if e.as_json.is_some() {
33756            self.write_keyword(" AS JSON");
33757        }
33758        Ok(())
33759    }
33760
33761    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
33762        // this OPERATOR(op) expression
33763        self.generate_expression(&e.this)?;
33764        self.write_space();
33765        if let Some(op) = &e.operator {
33766            self.write_keyword("OPERATOR");
33767            self.write("(");
33768            self.generate_expression(op)?;
33769            self.write(")");
33770        }
33771        // Emit inline comments between OPERATOR() and the RHS
33772        for comment in &e.comments {
33773            self.write_space();
33774            self.write_formatted_comment(comment);
33775        }
33776        self.write_space();
33777        self.generate_expression(&e.expression)?;
33778        Ok(())
33779    }
33780
33781    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
33782        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
33783        self.write_keyword("ORDER BY");
33784        let pretty_clickhouse_single_paren = self.config.pretty
33785            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
33786            && e.expressions.len() == 1
33787            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
33788        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
33789            && e.expressions.len() == 1
33790            && matches!(e.expressions[0].this, Expression::Tuple(_))
33791            && !e.expressions[0].desc
33792            && e.expressions[0].nulls_first.is_none();
33793
33794        if pretty_clickhouse_single_paren {
33795            self.write_space();
33796            if let Expression::Paren(p) = &e.expressions[0].this {
33797                self.write("(");
33798                self.write_newline();
33799                self.indent_level += 1;
33800                self.write_indent();
33801                self.generate_expression(&p.this)?;
33802                self.indent_level -= 1;
33803                self.write_newline();
33804                self.write(")");
33805            }
33806            return Ok(());
33807        }
33808
33809        if clickhouse_single_tuple {
33810            self.write_space();
33811            if let Expression::Tuple(t) = &e.expressions[0].this {
33812                self.write("(");
33813                for (i, expr) in t.expressions.iter().enumerate() {
33814                    if i > 0 {
33815                        self.write(", ");
33816                    }
33817                    self.generate_expression(expr)?;
33818                }
33819                self.write(")");
33820            }
33821            return Ok(());
33822        }
33823
33824        self.write_space();
33825        for (i, ordered) in e.expressions.iter().enumerate() {
33826            if i > 0 {
33827                self.write(", ");
33828            }
33829            self.generate_expression(&ordered.this)?;
33830            if ordered.desc {
33831                self.write_space();
33832                self.write_keyword("DESC");
33833            } else if ordered.explicit_asc {
33834                self.write_space();
33835                self.write_keyword("ASC");
33836            }
33837            if let Some(nulls_first) = ordered.nulls_first {
33838                // In Dremio, NULLS LAST is the default, so skip generating it
33839                let skip_nulls_last =
33840                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
33841                if !skip_nulls_last {
33842                    self.write_space();
33843                    self.write_keyword("NULLS");
33844                    self.write_space();
33845                    if nulls_first {
33846                        self.write_keyword("FIRST");
33847                    } else {
33848                        self.write_keyword("LAST");
33849                    }
33850                }
33851            }
33852        }
33853        Ok(())
33854    }
33855
33856    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
33857        // OUTPUT(model)
33858        self.write_keyword("OUTPUT");
33859        self.write("(");
33860        if self.config.pretty {
33861            self.indent_level += 1;
33862            self.write_newline();
33863            self.write_indent();
33864            self.generate_expression(&e.this)?;
33865            self.indent_level -= 1;
33866            self.write_newline();
33867        } else {
33868            self.generate_expression(&e.this)?;
33869        }
33870        self.write(")");
33871        Ok(())
33872    }
33873
33874    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
33875        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
33876        self.write_keyword("TRUNCATE");
33877        if let Some(this) = &e.this {
33878            self.write_space();
33879            self.generate_expression(this)?;
33880        }
33881        if e.with_count.is_some() {
33882            self.write_keyword(" WITH COUNT");
33883        } else {
33884            self.write_keyword(" WITHOUT COUNT");
33885        }
33886        Ok(())
33887    }
33888
33889    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
33890        // Python: name(expressions)(params)
33891        self.generate_expression(&e.this)?;
33892        self.write("(");
33893        for (i, expr) in e.expressions.iter().enumerate() {
33894            if i > 0 {
33895                self.write(", ");
33896            }
33897            self.generate_expression(expr)?;
33898        }
33899        self.write(")(");
33900        for (i, param) in e.params.iter().enumerate() {
33901            if i > 0 {
33902                self.write(", ");
33903            }
33904            self.generate_expression(param)?;
33905        }
33906        self.write(")");
33907        Ok(())
33908    }
33909
33910    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
33911        // PARSE_DATETIME(format, this) or similar
33912        self.write_keyword("PARSE_DATETIME");
33913        self.write("(");
33914        if let Some(format) = &e.format {
33915            self.write("'");
33916            self.write(format);
33917            self.write("', ");
33918        }
33919        self.generate_expression(&e.this)?;
33920        if let Some(zone) = &e.zone {
33921            self.write(", ");
33922            self.generate_expression(zone)?;
33923        }
33924        self.write(")");
33925        Ok(())
33926    }
33927
33928    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
33929        // PARSE_IP(this, type, permissive)
33930        self.write_keyword("PARSE_IP");
33931        self.write("(");
33932        self.generate_expression(&e.this)?;
33933        if let Some(type_) = &e.type_ {
33934            self.write(", ");
33935            self.generate_expression(type_)?;
33936        }
33937        if let Some(permissive) = &e.permissive {
33938            self.write(", ");
33939            self.generate_expression(permissive)?;
33940        }
33941        self.write(")");
33942        Ok(())
33943    }
33944
33945    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
33946        // PARSE_JSON(this, [expression])
33947        self.write_keyword("PARSE_JSON");
33948        self.write("(");
33949        self.generate_expression(&e.this)?;
33950        if let Some(expression) = &e.expression {
33951            self.write(", ");
33952            self.generate_expression(expression)?;
33953        }
33954        self.write(")");
33955        Ok(())
33956    }
33957
33958    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
33959        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
33960        self.write_keyword("PARSE_TIME");
33961        self.write("(");
33962        self.write(&format!("'{}'", e.format));
33963        self.write(", ");
33964        self.generate_expression(&e.this)?;
33965        self.write(")");
33966        Ok(())
33967    }
33968
33969    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
33970        // PARSE_URL(this, [part_to_extract], [key], [permissive])
33971        self.write_keyword("PARSE_URL");
33972        self.write("(");
33973        self.generate_expression(&e.this)?;
33974        if let Some(part) = &e.part_to_extract {
33975            self.write(", ");
33976            self.generate_expression(part)?;
33977        }
33978        if let Some(key) = &e.key {
33979            self.write(", ");
33980            self.generate_expression(key)?;
33981        }
33982        if let Some(permissive) = &e.permissive {
33983            self.write(", ");
33984            self.generate_expression(permissive)?;
33985        }
33986        self.write(")");
33987        Ok(())
33988    }
33989
33990    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
33991        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
33992        if e.subpartition {
33993            self.write_keyword("SUBPARTITION");
33994        } else {
33995            self.write_keyword("PARTITION");
33996        }
33997        self.write("(");
33998        for (i, expr) in e.expressions.iter().enumerate() {
33999            if i > 0 {
34000                self.write(", ");
34001            }
34002            self.generate_expression(expr)?;
34003        }
34004        self.write(")");
34005        Ok(())
34006    }
34007
34008    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
34009        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
34010        if let Some(this) = &e.this {
34011            if let Some(expression) = &e.expression {
34012                // WITH (MODULUS this, REMAINDER expression)
34013                self.write_keyword("WITH");
34014                self.write(" (");
34015                self.write_keyword("MODULUS");
34016                self.write_space();
34017                self.generate_expression(this)?;
34018                self.write(", ");
34019                self.write_keyword("REMAINDER");
34020                self.write_space();
34021                self.generate_expression(expression)?;
34022                self.write(")");
34023            } else {
34024                // IN (this) - this could be a list
34025                self.write_keyword("IN");
34026                self.write(" (");
34027                self.generate_partition_bound_values(this)?;
34028                self.write(")");
34029            }
34030        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
34031            // FROM (from_expressions) TO (to_expressions)
34032            self.write_keyword("FROM");
34033            self.write(" (");
34034            self.generate_partition_bound_values(from)?;
34035            self.write(") ");
34036            self.write_keyword("TO");
34037            self.write(" (");
34038            self.generate_partition_bound_values(to)?;
34039            self.write(")");
34040        }
34041        Ok(())
34042    }
34043
34044    /// Generate partition bound values - handles Tuple expressions by outputting
34045    /// contents without wrapping parens (since caller provides the parens)
34046    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
34047        if let Expression::Tuple(t) = expr {
34048            for (i, e) in t.expressions.iter().enumerate() {
34049                if i > 0 {
34050                    self.write(", ");
34051                }
34052                self.generate_expression(e)?;
34053            }
34054            Ok(())
34055        } else {
34056            self.generate_expression(expr)
34057        }
34058    }
34059
34060    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
34061        // PARTITION BY LIST (partition_expressions) (create_expressions)
34062        self.write_keyword("PARTITION BY LIST");
34063        if let Some(partition_exprs) = &e.partition_expressions {
34064            self.write(" (");
34065            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
34066            self.generate_doris_partition_expressions(partition_exprs)?;
34067            self.write(")");
34068        }
34069        if let Some(create_exprs) = &e.create_expressions {
34070            self.write(" (");
34071            // Unwrap Tuple for partition definitions
34072            self.generate_doris_partition_definitions(create_exprs)?;
34073            self.write(")");
34074        }
34075        Ok(())
34076    }
34077
34078    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
34079        // PARTITION BY RANGE (partition_expressions) (create_expressions)
34080        self.write_keyword("PARTITION BY RANGE");
34081        if let Some(partition_exprs) = &e.partition_expressions {
34082            self.write(" (");
34083            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
34084            self.generate_doris_partition_expressions(partition_exprs)?;
34085            self.write(")");
34086        }
34087        if let Some(create_exprs) = &e.create_expressions {
34088            self.write(" (");
34089            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
34090            self.generate_doris_partition_definitions(create_exprs)?;
34091            self.write(")");
34092        }
34093        Ok(())
34094    }
34095
34096    /// Generate Doris partition column expressions (unwrap Tuple)
34097    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
34098        if let Expression::Tuple(t) = expr {
34099            for (i, e) in t.expressions.iter().enumerate() {
34100                if i > 0 {
34101                    self.write(", ");
34102                }
34103                self.generate_expression(e)?;
34104            }
34105        } else {
34106            self.generate_expression(expr)?;
34107        }
34108        Ok(())
34109    }
34110
34111    /// Generate Doris partition definitions (comma-separated Partition expressions)
34112    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
34113        match expr {
34114            Expression::Tuple(t) => {
34115                // Multiple partitions, comma-separated
34116                for (i, part) in t.expressions.iter().enumerate() {
34117                    if i > 0 {
34118                        self.write(", ");
34119                    }
34120                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
34121                    if let Expression::Partition(p) = part {
34122                        for (j, inner) in p.expressions.iter().enumerate() {
34123                            if j > 0 {
34124                                self.write(", ");
34125                            }
34126                            self.generate_expression(inner)?;
34127                        }
34128                    } else {
34129                        self.generate_expression(part)?;
34130                    }
34131                }
34132            }
34133            Expression::PartitionByRangePropertyDynamic(_) => {
34134                // Dynamic partition - FROM/TO/INTERVAL
34135                self.generate_expression(expr)?;
34136            }
34137            _ => {
34138                self.generate_expression(expr)?;
34139            }
34140        }
34141        Ok(())
34142    }
34143
34144    fn generate_partition_by_range_property_dynamic(
34145        &mut self,
34146        e: &PartitionByRangePropertyDynamic,
34147    ) -> Result<()> {
34148        if e.use_start_end {
34149            // StarRocks: START ('val') END ('val') EVERY (expr)
34150            if let Some(start) = &e.start {
34151                self.write_keyword("START");
34152                self.write(" (");
34153                self.generate_expression(start)?;
34154                self.write(")");
34155            }
34156            if let Some(end) = &e.end {
34157                self.write_space();
34158                self.write_keyword("END");
34159                self.write(" (");
34160                self.generate_expression(end)?;
34161                self.write(")");
34162            }
34163            if let Some(every) = &e.every {
34164                self.write_space();
34165                self.write_keyword("EVERY");
34166                self.write(" (");
34167                // Use unquoted interval format for StarRocks
34168                self.generate_doris_interval(every)?;
34169                self.write(")");
34170            }
34171        } else {
34172            // Doris: FROM (start) TO (end) INTERVAL n UNIT
34173            if let Some(start) = &e.start {
34174                self.write_keyword("FROM");
34175                self.write(" (");
34176                self.generate_expression(start)?;
34177                self.write(")");
34178            }
34179            if let Some(end) = &e.end {
34180                self.write_space();
34181                self.write_keyword("TO");
34182                self.write(" (");
34183                self.generate_expression(end)?;
34184                self.write(")");
34185            }
34186            if let Some(every) = &e.every {
34187                self.write_space();
34188                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
34189                self.generate_doris_interval(every)?;
34190            }
34191        }
34192        Ok(())
34193    }
34194
34195    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
34196    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
34197        if let Expression::Interval(interval) = expr {
34198            self.write_keyword("INTERVAL");
34199            if let Some(ref value) = interval.this {
34200                self.write_space();
34201                // If the value is a string literal that looks like a number,
34202                // output it without quotes (matching Python sqlglot's
34203                // partitionbyrangepropertydynamic_sql which converts back to number)
34204                match value {
34205                    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()) => {
34206                        if let Literal::String(s) = lit.as_ref() {
34207                            self.write(s);
34208                        }
34209                    }
34210                    _ => {
34211                        self.generate_expression(value)?;
34212                    }
34213                }
34214            }
34215            if let Some(ref unit_spec) = interval.unit {
34216                self.write_space();
34217                self.write_interval_unit_spec(unit_spec)?;
34218            }
34219            Ok(())
34220        } else {
34221            self.generate_expression(expr)
34222        }
34223    }
34224
34225    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
34226        // TRUNCATE(expression, this)
34227        self.write_keyword("TRUNCATE");
34228        self.write("(");
34229        self.generate_expression(&e.expression)?;
34230        self.write(", ");
34231        self.generate_expression(&e.this)?;
34232        self.write(")");
34233        Ok(())
34234    }
34235
34236    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
34237        // Doris: PARTITION name VALUES IN (val1, val2)
34238        self.write_keyword("PARTITION");
34239        self.write_space();
34240        self.generate_expression(&e.this)?;
34241        self.write_space();
34242        self.write_keyword("VALUES IN");
34243        self.write(" (");
34244        for (i, expr) in e.expressions.iter().enumerate() {
34245            if i > 0 {
34246                self.write(", ");
34247            }
34248            self.generate_expression(expr)?;
34249        }
34250        self.write(")");
34251        Ok(())
34252    }
34253
34254    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
34255        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
34256        // TSQL ranges have no expressions and just use `this TO expression`
34257        if e.expressions.is_empty() && e.expression.is_some() {
34258            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
34259            self.generate_expression(&e.this)?;
34260            self.write_space();
34261            self.write_keyword("TO");
34262            self.write_space();
34263            self.generate_expression(e.expression.as_ref().unwrap())?;
34264            return Ok(());
34265        }
34266
34267        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
34268        self.write_keyword("PARTITION");
34269        self.write_space();
34270        self.generate_expression(&e.this)?;
34271        self.write_space();
34272
34273        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
34274        if e.expressions.len() == 1 {
34275            // Single value: VALUES LESS THAN (val)
34276            self.write_keyword("VALUES LESS THAN");
34277            self.write(" (");
34278            self.generate_expression(&e.expressions[0])?;
34279            self.write(")");
34280        } else if !e.expressions.is_empty() {
34281            // Multiple values with Tuple: VALUES [(val1), (val2))
34282            self.write_keyword("VALUES");
34283            self.write(" [");
34284            for (i, expr) in e.expressions.iter().enumerate() {
34285                if i > 0 {
34286                    self.write(", ");
34287                }
34288                // If the expr is a Tuple, generate its contents wrapped in parens
34289                if let Expression::Tuple(t) = expr {
34290                    self.write("(");
34291                    for (j, inner) in t.expressions.iter().enumerate() {
34292                        if j > 0 {
34293                            self.write(", ");
34294                        }
34295                        self.generate_expression(inner)?;
34296                    }
34297                    self.write(")");
34298                } else {
34299                    self.write("(");
34300                    self.generate_expression(expr)?;
34301                    self.write(")");
34302                }
34303            }
34304            self.write(")");
34305        }
34306        Ok(())
34307    }
34308
34309    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
34310        // BUCKET(this, expression)
34311        self.write_keyword("BUCKET");
34312        self.write("(");
34313        self.generate_expression(&e.this)?;
34314        self.write(", ");
34315        self.generate_expression(&e.expression)?;
34316        self.write(")");
34317        Ok(())
34318    }
34319
34320    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
34321        // BigQuery table property: PARTITION BY expression [, expression ...]
34322        self.write_keyword("PARTITION BY");
34323        self.write_space();
34324        for (i, expr) in e.expressions.iter().enumerate() {
34325            if i > 0 {
34326                self.write(", ");
34327            }
34328            self.generate_expression(expr)?;
34329        }
34330        Ok(())
34331    }
34332
34333    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
34334        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
34335        if matches!(
34336            self.config.dialect,
34337            Some(crate::dialects::DialectType::Teradata)
34338                | Some(crate::dialects::DialectType::ClickHouse)
34339        ) {
34340            self.write_keyword("PARTITION BY");
34341        } else {
34342            self.write_keyword("PARTITIONED BY");
34343        }
34344        self.write_space();
34345        // In pretty mode, always use multiline tuple format for PARTITIONED BY
34346        if self.config.pretty {
34347            if let Expression::Tuple(ref tuple) = *e.this {
34348                self.write("(");
34349                self.write_newline();
34350                self.indent_level += 1;
34351                for (i, expr) in tuple.expressions.iter().enumerate() {
34352                    if i > 0 {
34353                        self.write(",");
34354                        self.write_newline();
34355                    }
34356                    self.write_indent();
34357                    self.generate_expression(expr)?;
34358                }
34359                self.indent_level -= 1;
34360                self.write_newline();
34361                self.write(")");
34362            } else {
34363                self.generate_expression(&e.this)?;
34364            }
34365        } else {
34366            self.generate_expression(&e.this)?;
34367        }
34368        Ok(())
34369    }
34370
34371    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
34372        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
34373        self.write_keyword("PARTITION OF");
34374        self.write_space();
34375        self.generate_expression(&e.this)?;
34376        // Check if expression is a PartitionBoundSpec
34377        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
34378            self.write_space();
34379            self.write_keyword("FOR VALUES");
34380            self.write_space();
34381            self.generate_expression(&e.expression)?;
34382        } else {
34383            self.write_space();
34384            self.write_keyword("DEFAULT");
34385        }
34386        Ok(())
34387    }
34388
34389    fn generate_period_for_system_time_constraint(
34390        &mut self,
34391        e: &PeriodForSystemTimeConstraint,
34392    ) -> Result<()> {
34393        // PERIOD FOR SYSTEM_TIME (this, expression)
34394        self.write_keyword("PERIOD FOR SYSTEM_TIME");
34395        self.write(" (");
34396        self.generate_expression(&e.this)?;
34397        self.write(", ");
34398        self.generate_expression(&e.expression)?;
34399        self.write(")");
34400        Ok(())
34401    }
34402
34403    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
34404        // value AS alias
34405        // The alias can be an identifier or an expression (e.g., string concatenation)
34406        self.generate_expression(&e.this)?;
34407        self.write_space();
34408        self.write_keyword("AS");
34409        self.write_space();
34410        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
34411        if self.config.unpivot_aliases_are_identifiers {
34412            match &e.alias {
34413                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
34414                    let Literal::String(s) = lit.as_ref() else {
34415                        unreachable!()
34416                    };
34417                    // Convert string literal to identifier
34418                    self.generate_identifier(&Identifier::new(s.clone()))?;
34419                }
34420                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
34421                    let Literal::Number(n) = lit.as_ref() else {
34422                        unreachable!()
34423                    };
34424                    // Convert number literal to quoted identifier
34425                    let mut id = Identifier::new(n.clone());
34426                    id.quoted = true;
34427                    self.generate_identifier(&id)?;
34428                }
34429                other => {
34430                    self.generate_expression(other)?;
34431                }
34432            }
34433        } else {
34434            self.generate_expression(&e.alias)?;
34435        }
34436        Ok(())
34437    }
34438
34439    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
34440        // ANY or ANY [expression]
34441        self.write_keyword("ANY");
34442        if let Some(this) = &e.this {
34443            self.write_space();
34444            self.generate_expression(this)?;
34445        }
34446        Ok(())
34447    }
34448
34449    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
34450        // ML.PREDICT(MODEL this, expression, [params_struct])
34451        self.write_keyword("ML.PREDICT");
34452        self.write("(");
34453        self.write_keyword("MODEL");
34454        self.write_space();
34455        self.generate_expression(&e.this)?;
34456        self.write(", ");
34457        self.generate_expression(&e.expression)?;
34458        if let Some(params) = &e.params_struct {
34459            self.write(", ");
34460            self.generate_expression(params)?;
34461        }
34462        self.write(")");
34463        Ok(())
34464    }
34465
34466    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
34467        // PREVIOUS_DAY(this, expression)
34468        self.write_keyword("PREVIOUS_DAY");
34469        self.write("(");
34470        self.generate_expression(&e.this)?;
34471        self.write(", ");
34472        self.generate_expression(&e.expression)?;
34473        self.write(")");
34474        Ok(())
34475    }
34476
34477    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
34478        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
34479        self.write_keyword("PRIMARY KEY");
34480        if let Some(name) = &e.this {
34481            self.write_space();
34482            self.generate_expression(name)?;
34483        }
34484        if !e.expressions.is_empty() {
34485            self.write(" (");
34486            for (i, expr) in e.expressions.iter().enumerate() {
34487                if i > 0 {
34488                    self.write(", ");
34489                }
34490                self.generate_expression(expr)?;
34491            }
34492            self.write(")");
34493        }
34494        if let Some(include) = &e.include {
34495            self.write_space();
34496            self.generate_expression(include)?;
34497        }
34498        if !e.options.is_empty() {
34499            self.write_space();
34500            for (i, opt) in e.options.iter().enumerate() {
34501                if i > 0 {
34502                    self.write_space();
34503                }
34504                self.generate_expression(opt)?;
34505            }
34506        }
34507        Ok(())
34508    }
34509
34510    fn generate_primary_key_column_constraint(
34511        &mut self,
34512        _e: &PrimaryKeyColumnConstraint,
34513    ) -> Result<()> {
34514        // PRIMARY KEY constraint at column level
34515        self.write_keyword("PRIMARY KEY");
34516        Ok(())
34517    }
34518
34519    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
34520        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
34521        self.write_keyword("PATH");
34522        self.write_space();
34523        self.generate_expression(&e.this)?;
34524        Ok(())
34525    }
34526
34527    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
34528        // PROJECTION this (expression)
34529        self.write_keyword("PROJECTION");
34530        self.write_space();
34531        self.generate_expression(&e.this)?;
34532        self.write(" (");
34533        self.generate_expression(&e.expression)?;
34534        self.write(")");
34535        Ok(())
34536    }
34537
34538    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
34539        // Properties list
34540        for (i, prop) in e.expressions.iter().enumerate() {
34541            if i > 0 {
34542                self.write(", ");
34543            }
34544            self.generate_expression(prop)?;
34545        }
34546        Ok(())
34547    }
34548
34549    fn generate_property(&mut self, e: &Property) -> Result<()> {
34550        // name=value
34551        self.generate_expression(&e.this)?;
34552        if let Some(value) = &e.value {
34553            self.write("=");
34554            self.generate_expression(value)?;
34555        }
34556        Ok(())
34557    }
34558
34559    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
34560        self.write_keyword("OPTIONS");
34561        if e.entries.is_empty() {
34562            self.write(" ()");
34563            return Ok(());
34564        }
34565
34566        if self.config.pretty {
34567            self.write(" (");
34568            self.write_newline();
34569            self.indent_level += 1;
34570            for (i, entry) in e.entries.iter().enumerate() {
34571                if i > 0 {
34572                    self.write(",");
34573                    self.write_newline();
34574                }
34575                self.write_indent();
34576                self.generate_identifier(&entry.key)?;
34577                self.write("=");
34578                self.generate_expression(&entry.value)?;
34579            }
34580            self.indent_level -= 1;
34581            self.write_newline();
34582            self.write(")");
34583        } else {
34584            self.write(" (");
34585            for (i, entry) in e.entries.iter().enumerate() {
34586                if i > 0 {
34587                    self.write(", ");
34588                }
34589                self.generate_identifier(&entry.key)?;
34590                self.write("=");
34591                self.generate_expression(&entry.value)?;
34592            }
34593            self.write(")");
34594        }
34595        Ok(())
34596    }
34597
34598    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
34599    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
34600        self.write_keyword("OPTIONS");
34601        self.write(" (");
34602        for (i, opt) in options.iter().enumerate() {
34603            if i > 0 {
34604                self.write(", ");
34605            }
34606            self.generate_option_expression(opt)?;
34607        }
34608        self.write(")");
34609        Ok(())
34610    }
34611
34612    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
34613    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
34614        self.write_keyword("PROPERTIES");
34615        self.write(" (");
34616        for (i, prop) in properties.iter().enumerate() {
34617            if i > 0 {
34618                self.write(", ");
34619            }
34620            self.generate_option_expression(prop)?;
34621        }
34622        self.write(")");
34623        Ok(())
34624    }
34625
34626    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
34627    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
34628        self.write_keyword("ENVIRONMENT");
34629        self.write(" (");
34630        for (i, env_item) in environment.iter().enumerate() {
34631            if i > 0 {
34632                self.write(", ");
34633            }
34634            self.generate_environment_expression(env_item)?;
34635        }
34636        self.write(")");
34637        Ok(())
34638    }
34639
34640    /// Generate an environment expression with spaces around =
34641    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
34642        match expr {
34643            Expression::Eq(eq) => {
34644                // Generate key = value with spaces (Databricks ENVIRONMENT style)
34645                self.generate_expression(&eq.left)?;
34646                self.write(" = ");
34647                self.generate_expression(&eq.right)?;
34648                Ok(())
34649            }
34650            _ => self.generate_expression(expr),
34651        }
34652    }
34653
34654    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
34655    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
34656        self.write_keyword("TBLPROPERTIES");
34657        if self.config.pretty {
34658            self.write(" (");
34659            self.write_newline();
34660            self.indent_level += 1;
34661            for (i, opt) in options.iter().enumerate() {
34662                if i > 0 {
34663                    self.write(",");
34664                    self.write_newline();
34665                }
34666                self.write_indent();
34667                self.generate_option_expression(opt)?;
34668            }
34669            self.indent_level -= 1;
34670            self.write_newline();
34671            self.write(")");
34672        } else {
34673            self.write(" (");
34674            for (i, opt) in options.iter().enumerate() {
34675                if i > 0 {
34676                    self.write(", ");
34677                }
34678                self.generate_option_expression(opt)?;
34679            }
34680            self.write(")");
34681        }
34682        Ok(())
34683    }
34684
34685    /// Generate an option expression without spaces around =
34686    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
34687        match expr {
34688            Expression::Eq(eq) => {
34689                // Generate key=value without spaces
34690                self.generate_expression(&eq.left)?;
34691                self.write("=");
34692                self.generate_expression(&eq.right)?;
34693                Ok(())
34694            }
34695            _ => self.generate_expression(expr),
34696        }
34697    }
34698
34699    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
34700        // Just output the name
34701        self.generate_expression(&e.this)?;
34702        Ok(())
34703    }
34704
34705    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
34706        // PUT source_file @stage [options]
34707        self.write_keyword("PUT");
34708        self.write_space();
34709
34710        // Source file path - preserve original quoting
34711        if e.source_quoted {
34712            self.write("'");
34713            self.write(&e.source);
34714            self.write("'");
34715        } else {
34716            self.write(&e.source);
34717        }
34718
34719        self.write_space();
34720
34721        // Target stage reference - output the string directly (includes @)
34722        if let Expression::Literal(lit) = &e.target {
34723            if let Literal::String(s) = lit.as_ref() {
34724                self.write(s);
34725            }
34726        } else {
34727            self.generate_expression(&e.target)?;
34728        }
34729
34730        // Optional parameters: KEY=VALUE
34731        for param in &e.params {
34732            self.write_space();
34733            self.write(&param.name);
34734            if let Some(ref value) = param.value {
34735                self.write("=");
34736                self.generate_expression(value)?;
34737            }
34738        }
34739
34740        Ok(())
34741    }
34742
34743    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
34744        // QUANTILE(this, quantile)
34745        self.write_keyword("QUANTILE");
34746        self.write("(");
34747        self.generate_expression(&e.this)?;
34748        if let Some(quantile) = &e.quantile {
34749            self.write(", ");
34750            self.generate_expression(quantile)?;
34751        }
34752        self.write(")");
34753        Ok(())
34754    }
34755
34756    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
34757        // QUERY_BAND = this [UPDATE] [FOR scope]
34758        if matches!(
34759            self.config.dialect,
34760            Some(crate::dialects::DialectType::Teradata)
34761        ) {
34762            self.write_keyword("SET");
34763            self.write_space();
34764        }
34765        self.write_keyword("QUERY_BAND");
34766        self.write(" = ");
34767        self.generate_expression(&e.this)?;
34768        if e.update.is_some() {
34769            self.write_space();
34770            self.write_keyword("UPDATE");
34771        }
34772        if let Some(scope) = &e.scope {
34773            self.write_space();
34774            self.write_keyword("FOR");
34775            self.write_space();
34776            self.generate_expression(scope)?;
34777        }
34778        Ok(())
34779    }
34780
34781    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
34782        // this = expression
34783        self.generate_expression(&e.this)?;
34784        if let Some(expression) = &e.expression {
34785            self.write(" = ");
34786            self.generate_expression(expression)?;
34787        }
34788        Ok(())
34789    }
34790
34791    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
34792        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
34793        self.write_keyword("TRANSFORM");
34794        self.write("(");
34795        for (i, expr) in e.expressions.iter().enumerate() {
34796            if i > 0 {
34797                self.write(", ");
34798            }
34799            self.generate_expression(expr)?;
34800        }
34801        self.write(")");
34802        if let Some(row_format_before) = &e.row_format_before {
34803            self.write_space();
34804            self.generate_expression(row_format_before)?;
34805        }
34806        if let Some(record_writer) = &e.record_writer {
34807            self.write_space();
34808            self.write_keyword("RECORDWRITER");
34809            self.write_space();
34810            self.generate_expression(record_writer)?;
34811        }
34812        if let Some(command_script) = &e.command_script {
34813            self.write_space();
34814            self.write_keyword("USING");
34815            self.write_space();
34816            self.generate_expression(command_script)?;
34817        }
34818        if let Some(schema) = &e.schema {
34819            self.write_space();
34820            self.write_keyword("AS");
34821            self.write_space();
34822            self.generate_expression(schema)?;
34823        }
34824        if let Some(row_format_after) = &e.row_format_after {
34825            self.write_space();
34826            self.generate_expression(row_format_after)?;
34827        }
34828        if let Some(record_reader) = &e.record_reader {
34829            self.write_space();
34830            self.write_keyword("RECORDREADER");
34831            self.write_space();
34832            self.generate_expression(record_reader)?;
34833        }
34834        Ok(())
34835    }
34836
34837    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
34838        // RANDN([seed])
34839        self.write_keyword("RANDN");
34840        self.write("(");
34841        if let Some(this) = &e.this {
34842            self.generate_expression(this)?;
34843        }
34844        self.write(")");
34845        Ok(())
34846    }
34847
34848    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
34849        // RANDSTR(this, [generator])
34850        self.write_keyword("RANDSTR");
34851        self.write("(");
34852        self.generate_expression(&e.this)?;
34853        if let Some(generator) = &e.generator {
34854            self.write(", ");
34855            self.generate_expression(generator)?;
34856        }
34857        self.write(")");
34858        Ok(())
34859    }
34860
34861    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
34862        // RANGE_BUCKET(this, expression)
34863        self.write_keyword("RANGE_BUCKET");
34864        self.write("(");
34865        self.generate_expression(&e.this)?;
34866        self.write(", ");
34867        self.generate_expression(&e.expression)?;
34868        self.write(")");
34869        Ok(())
34870    }
34871
34872    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
34873        // RANGE_N(this BETWEEN expressions [EACH each])
34874        self.write_keyword("RANGE_N");
34875        self.write("(");
34876        self.generate_expression(&e.this)?;
34877        self.write_space();
34878        self.write_keyword("BETWEEN");
34879        self.write_space();
34880        for (i, expr) in e.expressions.iter().enumerate() {
34881            if i > 0 {
34882                self.write(", ");
34883            }
34884            self.generate_expression(expr)?;
34885        }
34886        if let Some(each) = &e.each {
34887            self.write_space();
34888            self.write_keyword("EACH");
34889            self.write_space();
34890            self.generate_expression(each)?;
34891        }
34892        self.write(")");
34893        Ok(())
34894    }
34895
34896    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
34897        // READ_CSV(this, expressions...)
34898        self.write_keyword("READ_CSV");
34899        self.write("(");
34900        self.generate_expression(&e.this)?;
34901        for expr in &e.expressions {
34902            self.write(", ");
34903            self.generate_expression(expr)?;
34904        }
34905        self.write(")");
34906        Ok(())
34907    }
34908
34909    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
34910        // READ_PARQUET(expressions...)
34911        self.write_keyword("READ_PARQUET");
34912        self.write("(");
34913        for (i, expr) in e.expressions.iter().enumerate() {
34914            if i > 0 {
34915                self.write(", ");
34916            }
34917            self.generate_expression(expr)?;
34918        }
34919        self.write(")");
34920        Ok(())
34921    }
34922
34923    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
34924        // SEARCH kind FIRST BY this SET expression [USING using]
34925        // or CYCLE this SET expression [USING using]
34926        if e.kind == "CYCLE" {
34927            self.write_keyword("CYCLE");
34928        } else {
34929            self.write_keyword("SEARCH");
34930            self.write_space();
34931            self.write(&e.kind);
34932            self.write_space();
34933            self.write_keyword("FIRST BY");
34934        }
34935        self.write_space();
34936        self.generate_expression(&e.this)?;
34937        self.write_space();
34938        self.write_keyword("SET");
34939        self.write_space();
34940        self.generate_expression(&e.expression)?;
34941        if let Some(using) = &e.using {
34942            self.write_space();
34943            self.write_keyword("USING");
34944            self.write_space();
34945            self.generate_expression(using)?;
34946        }
34947        Ok(())
34948    }
34949
34950    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
34951        // REDUCE(this, initial, merge, [finish])
34952        self.write_keyword("REDUCE");
34953        self.write("(");
34954        self.generate_expression(&e.this)?;
34955        if let Some(initial) = &e.initial {
34956            self.write(", ");
34957            self.generate_expression(initial)?;
34958        }
34959        if let Some(merge) = &e.merge {
34960            self.write(", ");
34961            self.generate_expression(merge)?;
34962        }
34963        if let Some(finish) = &e.finish {
34964            self.write(", ");
34965            self.generate_expression(finish)?;
34966        }
34967        self.write(")");
34968        Ok(())
34969    }
34970
34971    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
34972        // REFERENCES this (expressions) [options]
34973        self.write_keyword("REFERENCES");
34974        self.write_space();
34975        self.generate_expression(&e.this)?;
34976        if !e.expressions.is_empty() {
34977            self.write(" (");
34978            for (i, expr) in e.expressions.iter().enumerate() {
34979                if i > 0 {
34980                    self.write(", ");
34981                }
34982                self.generate_expression(expr)?;
34983            }
34984            self.write(")");
34985        }
34986        for opt in &e.options {
34987            self.write_space();
34988            self.generate_expression(opt)?;
34989        }
34990        Ok(())
34991    }
34992
34993    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
34994        // REFRESH [kind] this
34995        self.write_keyword("REFRESH");
34996        if !e.kind.is_empty() {
34997            self.write_space();
34998            self.write_keyword(&e.kind);
34999        }
35000        self.write_space();
35001        self.generate_expression(&e.this)?;
35002        Ok(())
35003    }
35004
35005    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
35006        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
35007        self.write_keyword("REFRESH");
35008        self.write_space();
35009        self.write_keyword(&e.method);
35010
35011        if let Some(ref kind) = e.kind {
35012            self.write_space();
35013            self.write_keyword("ON");
35014            self.write_space();
35015            self.write_keyword(kind);
35016
35017            // EVERY n UNIT
35018            if let Some(ref every) = e.every {
35019                self.write_space();
35020                self.write_keyword("EVERY");
35021                self.write_space();
35022                self.generate_expression(every)?;
35023                if let Some(ref unit) = e.unit {
35024                    self.write_space();
35025                    self.write_keyword(unit);
35026                }
35027            }
35028
35029            // STARTS 'datetime'
35030            if let Some(ref starts) = e.starts {
35031                self.write_space();
35032                self.write_keyword("STARTS");
35033                self.write_space();
35034                self.generate_expression(starts)?;
35035            }
35036        }
35037        Ok(())
35038    }
35039
35040    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
35041        // REGEXP_COUNT(this, expression, position, parameters)
35042        self.write_keyword("REGEXP_COUNT");
35043        self.write("(");
35044        self.generate_expression(&e.this)?;
35045        self.write(", ");
35046        self.generate_expression(&e.expression)?;
35047        if let Some(position) = &e.position {
35048            self.write(", ");
35049            self.generate_expression(position)?;
35050        }
35051        if let Some(parameters) = &e.parameters {
35052            self.write(", ");
35053            self.generate_expression(parameters)?;
35054        }
35055        self.write(")");
35056        Ok(())
35057    }
35058
35059    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
35060        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
35061        self.write_keyword("REGEXP_EXTRACT_ALL");
35062        self.write("(");
35063        self.generate_expression(&e.this)?;
35064        self.write(", ");
35065        self.generate_expression(&e.expression)?;
35066        if let Some(group) = &e.group {
35067            self.write(", ");
35068            self.generate_expression(group)?;
35069        }
35070        self.write(")");
35071        Ok(())
35072    }
35073
35074    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
35075        // REGEXP_FULL_MATCH(this, expression)
35076        self.write_keyword("REGEXP_FULL_MATCH");
35077        self.write("(");
35078        self.generate_expression(&e.this)?;
35079        self.write(", ");
35080        self.generate_expression(&e.expression)?;
35081        self.write(")");
35082        Ok(())
35083    }
35084
35085    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
35086        use crate::dialects::DialectType;
35087        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
35088        if matches!(
35089            self.config.dialect,
35090            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
35091        ) && e.flag.is_none()
35092        {
35093            self.generate_expression(&e.this)?;
35094            self.write(" ~* ");
35095            self.generate_expression(&e.expression)?;
35096        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && e.flag.is_none() {
35097            // ClickHouse has no case-insensitive regex operator; use match() with an inline
35098            // (?i) flag. Inline it into a string-literal pattern for readable output, and fall
35099            // back to concat() so the flag also applies to a non-literal pattern expression.
35100            self.write("match(");
35101            self.generate_expression(&e.this)?;
35102            self.write(", ");
35103            if let Expression::Literal(lit) = e.expression.as_ref() {
35104                if let Literal::String(s) = lit.as_ref() {
35105                    let insensitive =
35106                        Expression::Literal(Box::new(Literal::String(format!("(?i){s}"))));
35107                    self.generate_expression(&insensitive)?;
35108                    self.write(")");
35109                    return Ok(());
35110                }
35111            }
35112            self.write("concat('(?i)', ");
35113            self.generate_expression(&e.expression)?;
35114            self.write("))");
35115            return Ok(());
35116        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35117            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
35118            self.write_keyword("REGEXP_LIKE");
35119            self.write("(");
35120            self.generate_expression(&e.this)?;
35121            self.write(", ");
35122            self.generate_expression(&e.expression)?;
35123            self.write(", ");
35124            if let Some(flag) = &e.flag {
35125                self.generate_expression(flag)?;
35126            } else {
35127                self.write("'i'");
35128            }
35129            self.write(")");
35130        } else {
35131            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
35132            self.generate_expression(&e.this)?;
35133            self.write_space();
35134            self.write_keyword("REGEXP_ILIKE");
35135            self.write_space();
35136            self.generate_expression(&e.expression)?;
35137            if let Some(flag) = &e.flag {
35138                self.write(", ");
35139                self.generate_expression(flag)?;
35140            }
35141        }
35142        Ok(())
35143    }
35144
35145    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
35146        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
35147        self.write_keyword("REGEXP_INSTR");
35148        self.write("(");
35149        self.generate_expression(&e.this)?;
35150        self.write(", ");
35151        self.generate_expression(&e.expression)?;
35152        if let Some(position) = &e.position {
35153            self.write(", ");
35154            self.generate_expression(position)?;
35155        }
35156        if let Some(occurrence) = &e.occurrence {
35157            self.write(", ");
35158            self.generate_expression(occurrence)?;
35159        }
35160        if let Some(option) = &e.option {
35161            self.write(", ");
35162            self.generate_expression(option)?;
35163        }
35164        if let Some(parameters) = &e.parameters {
35165            self.write(", ");
35166            self.generate_expression(parameters)?;
35167        }
35168        if let Some(group) = &e.group {
35169            self.write(", ");
35170            self.generate_expression(group)?;
35171        }
35172        self.write(")");
35173        Ok(())
35174    }
35175
35176    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
35177        // REGEXP_SPLIT(this, expression, limit)
35178        self.write_keyword("REGEXP_SPLIT");
35179        self.write("(");
35180        self.generate_expression(&e.this)?;
35181        self.write(", ");
35182        self.generate_expression(&e.expression)?;
35183        if let Some(limit) = &e.limit {
35184            self.write(", ");
35185            self.generate_expression(limit)?;
35186        }
35187        self.write(")");
35188        Ok(())
35189    }
35190
35191    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
35192        // REGR_AVGX(this, expression)
35193        self.write_keyword("REGR_AVGX");
35194        self.write("(");
35195        self.generate_expression(&e.this)?;
35196        self.write(", ");
35197        self.generate_expression(&e.expression)?;
35198        self.write(")");
35199        Ok(())
35200    }
35201
35202    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
35203        // REGR_AVGY(this, expression)
35204        self.write_keyword("REGR_AVGY");
35205        self.write("(");
35206        self.generate_expression(&e.this)?;
35207        self.write(", ");
35208        self.generate_expression(&e.expression)?;
35209        self.write(")");
35210        Ok(())
35211    }
35212
35213    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
35214        // REGR_COUNT(this, expression)
35215        self.write_keyword("REGR_COUNT");
35216        self.write("(");
35217        self.generate_expression(&e.this)?;
35218        self.write(", ");
35219        self.generate_expression(&e.expression)?;
35220        self.write(")");
35221        Ok(())
35222    }
35223
35224    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
35225        // REGR_INTERCEPT(this, expression)
35226        self.write_keyword("REGR_INTERCEPT");
35227        self.write("(");
35228        self.generate_expression(&e.this)?;
35229        self.write(", ");
35230        self.generate_expression(&e.expression)?;
35231        self.write(")");
35232        Ok(())
35233    }
35234
35235    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
35236        // REGR_R2(this, expression)
35237        self.write_keyword("REGR_R2");
35238        self.write("(");
35239        self.generate_expression(&e.this)?;
35240        self.write(", ");
35241        self.generate_expression(&e.expression)?;
35242        self.write(")");
35243        Ok(())
35244    }
35245
35246    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
35247        // REGR_SLOPE(this, expression)
35248        self.write_keyword("REGR_SLOPE");
35249        self.write("(");
35250        self.generate_expression(&e.this)?;
35251        self.write(", ");
35252        self.generate_expression(&e.expression)?;
35253        self.write(")");
35254        Ok(())
35255    }
35256
35257    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
35258        // REGR_SXX(this, expression)
35259        self.write_keyword("REGR_SXX");
35260        self.write("(");
35261        self.generate_expression(&e.this)?;
35262        self.write(", ");
35263        self.generate_expression(&e.expression)?;
35264        self.write(")");
35265        Ok(())
35266    }
35267
35268    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
35269        // REGR_SXY(this, expression)
35270        self.write_keyword("REGR_SXY");
35271        self.write("(");
35272        self.generate_expression(&e.this)?;
35273        self.write(", ");
35274        self.generate_expression(&e.expression)?;
35275        self.write(")");
35276        Ok(())
35277    }
35278
35279    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
35280        // REGR_SYY(this, expression)
35281        self.write_keyword("REGR_SYY");
35282        self.write("(");
35283        self.generate_expression(&e.this)?;
35284        self.write(", ");
35285        self.generate_expression(&e.expression)?;
35286        self.write(")");
35287        Ok(())
35288    }
35289
35290    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
35291        // REGR_VALX(this, expression)
35292        self.write_keyword("REGR_VALX");
35293        self.write("(");
35294        self.generate_expression(&e.this)?;
35295        self.write(", ");
35296        self.generate_expression(&e.expression)?;
35297        self.write(")");
35298        Ok(())
35299    }
35300
35301    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
35302        // REGR_VALY(this, expression)
35303        self.write_keyword("REGR_VALY");
35304        self.write("(");
35305        self.generate_expression(&e.this)?;
35306        self.write(", ");
35307        self.generate_expression(&e.expression)?;
35308        self.write(")");
35309        Ok(())
35310    }
35311
35312    fn generate_remote_with_connection_model_property(
35313        &mut self,
35314        e: &RemoteWithConnectionModelProperty,
35315    ) -> Result<()> {
35316        // REMOTE WITH CONNECTION this
35317        self.write_keyword("REMOTE WITH CONNECTION");
35318        self.write_space();
35319        self.generate_expression(&e.this)?;
35320        Ok(())
35321    }
35322
35323    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
35324        // RENAME COLUMN [IF EXISTS] this TO new_name
35325        self.write_keyword("RENAME COLUMN");
35326        if e.exists {
35327            self.write_space();
35328            self.write_keyword("IF EXISTS");
35329        }
35330        self.write_space();
35331        self.generate_expression(&e.this)?;
35332        if let Some(to) = &e.to {
35333            self.write_space();
35334            self.write_keyword("TO");
35335            self.write_space();
35336            self.generate_expression(to)?;
35337        }
35338        Ok(())
35339    }
35340
35341    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
35342        // REPLACE PARTITION expression [FROM source]
35343        self.write_keyword("REPLACE PARTITION");
35344        self.write_space();
35345        self.generate_expression(&e.expression)?;
35346        if let Some(source) = &e.source {
35347            self.write_space();
35348            self.write_keyword("FROM");
35349            self.write_space();
35350            self.generate_expression(source)?;
35351        }
35352        Ok(())
35353    }
35354
35355    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
35356        // RETURNING expressions [INTO into]
35357        // TSQL and Fabric use OUTPUT instead of RETURNING
35358        let keyword = match self.config.dialect {
35359            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
35360            _ => "RETURNING",
35361        };
35362        self.write_keyword(keyword);
35363        self.write_space();
35364        for (i, expr) in e.expressions.iter().enumerate() {
35365            if i > 0 {
35366                self.write(", ");
35367            }
35368            self.generate_expression(expr)?;
35369        }
35370        if let Some(into) = &e.into {
35371            self.write_space();
35372            self.write_keyword("INTO");
35373            self.write_space();
35374            self.generate_expression(into)?;
35375        }
35376        Ok(())
35377    }
35378
35379    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
35380        // OUTPUT expressions [INTO into_table]
35381        self.write_space();
35382        self.write_keyword("OUTPUT");
35383        self.write_space();
35384        for (i, expr) in output.columns.iter().enumerate() {
35385            if i > 0 {
35386                self.write(", ");
35387            }
35388            self.generate_expression(expr)?;
35389        }
35390        if let Some(into_table) = &output.into_table {
35391            self.write_space();
35392            self.write_keyword("INTO");
35393            self.write_space();
35394            self.generate_expression(into_table)?;
35395        }
35396        Ok(())
35397    }
35398
35399    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
35400        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
35401        self.write_keyword("RETURNS");
35402        if e.is_table.is_some() {
35403            self.write_space();
35404            self.write_keyword("TABLE");
35405        }
35406        if let Some(table) = &e.table {
35407            self.write_space();
35408            self.generate_expression(table)?;
35409        } else if let Some(this) = &e.this {
35410            self.write_space();
35411            self.generate_expression(this)?;
35412        }
35413        if e.null.is_some() {
35414            self.write_space();
35415            self.write_keyword("NULL ON NULL INPUT");
35416        }
35417        Ok(())
35418    }
35419
35420    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
35421        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
35422        self.write_keyword("ROLLBACK");
35423
35424        // TSQL always uses ROLLBACK TRANSACTION
35425        if e.this.is_none()
35426            && matches!(
35427                self.config.dialect,
35428                Some(DialectType::TSQL) | Some(DialectType::Fabric)
35429            )
35430        {
35431            self.write_space();
35432            self.write_keyword("TRANSACTION");
35433        }
35434
35435        // Check if this has TRANSACTION keyword or transaction name
35436        if let Some(this) = &e.this {
35437            // Check if it's just the "TRANSACTION" marker or an actual transaction name
35438            let is_transaction_marker = matches!(
35439                this.as_ref(),
35440                Expression::Identifier(id) if id.name == "TRANSACTION"
35441            );
35442
35443            self.write_space();
35444            self.write_keyword("TRANSACTION");
35445
35446            // If it's a real transaction name, output it
35447            if !is_transaction_marker {
35448                self.write_space();
35449                self.generate_expression(this)?;
35450            }
35451        }
35452
35453        // Output TO savepoint
35454        if let Some(savepoint) = &e.savepoint {
35455            self.write_space();
35456            self.write_keyword("TO");
35457            self.write_space();
35458            self.generate_expression(savepoint)?;
35459        }
35460        Ok(())
35461    }
35462
35463    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
35464        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
35465        if e.expressions.is_empty() {
35466            self.write_keyword("WITH ROLLUP");
35467        } else {
35468            self.write_keyword("ROLLUP");
35469            self.write("(");
35470            for (i, expr) in e.expressions.iter().enumerate() {
35471                if i > 0 {
35472                    self.write(", ");
35473                }
35474                self.generate_expression(expr)?;
35475            }
35476            self.write(")");
35477        }
35478        Ok(())
35479    }
35480
35481    fn generate_row_format_delimited_property(
35482        &mut self,
35483        e: &RowFormatDelimitedProperty,
35484    ) -> Result<()> {
35485        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
35486        self.write_keyword("ROW FORMAT DELIMITED");
35487        if let Some(fields) = &e.fields {
35488            self.write_space();
35489            self.write_keyword("FIELDS TERMINATED BY");
35490            self.write_space();
35491            self.generate_expression(fields)?;
35492        }
35493        if let Some(escaped) = &e.escaped {
35494            self.write_space();
35495            self.write_keyword("ESCAPED BY");
35496            self.write_space();
35497            self.generate_expression(escaped)?;
35498        }
35499        if let Some(items) = &e.collection_items {
35500            self.write_space();
35501            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
35502            self.write_space();
35503            self.generate_expression(items)?;
35504        }
35505        if let Some(keys) = &e.map_keys {
35506            self.write_space();
35507            self.write_keyword("MAP KEYS TERMINATED BY");
35508            self.write_space();
35509            self.generate_expression(keys)?;
35510        }
35511        if let Some(lines) = &e.lines {
35512            self.write_space();
35513            self.write_keyword("LINES TERMINATED BY");
35514            self.write_space();
35515            self.generate_expression(lines)?;
35516        }
35517        if let Some(null) = &e.null {
35518            self.write_space();
35519            self.write_keyword("NULL DEFINED AS");
35520            self.write_space();
35521            self.generate_expression(null)?;
35522        }
35523        if let Some(serde) = &e.serde {
35524            self.write_space();
35525            self.generate_expression(serde)?;
35526        }
35527        Ok(())
35528    }
35529
35530    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
35531        // ROW FORMAT this
35532        self.write_keyword("ROW FORMAT");
35533        self.write_space();
35534        self.generate_expression(&e.this)?;
35535        Ok(())
35536    }
35537
35538    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
35539        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
35540        self.write_keyword("ROW FORMAT SERDE");
35541        self.write_space();
35542        self.generate_expression(&e.this)?;
35543        if let Some(props) = &e.serde_properties {
35544            self.write_space();
35545            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
35546            self.generate_expression(props)?;
35547        }
35548        Ok(())
35549    }
35550
35551    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
35552        // SHA2(this, length)
35553        self.write_keyword("SHA2");
35554        self.write("(");
35555        self.generate_expression(&e.this)?;
35556        if let Some(length) = e.length {
35557            self.write(", ");
35558            self.write(&length.to_string());
35559        }
35560        self.write(")");
35561        Ok(())
35562    }
35563
35564    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
35565        // SHA2_DIGEST(this, length)
35566        self.write_keyword("SHA2_DIGEST");
35567        self.write("(");
35568        self.generate_expression(&e.this)?;
35569        if let Some(length) = e.length {
35570            self.write(", ");
35571            self.write(&length.to_string());
35572        }
35573        self.write(")");
35574        Ok(())
35575    }
35576
35577    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
35578        let name = if matches!(
35579            self.config.dialect,
35580            Some(crate::dialects::DialectType::Spark)
35581                | Some(crate::dialects::DialectType::Databricks)
35582        ) {
35583            "TRY_ADD"
35584        } else {
35585            "SAFE_ADD"
35586        };
35587        self.write_keyword(name);
35588        self.write("(");
35589        self.generate_expression(&e.this)?;
35590        self.write(", ");
35591        self.generate_expression(&e.expression)?;
35592        self.write(")");
35593        Ok(())
35594    }
35595
35596    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
35597        // SAFE_DIVIDE(this, expression)
35598        self.write_keyword("SAFE_DIVIDE");
35599        self.write("(");
35600        self.generate_expression(&e.this)?;
35601        self.write(", ");
35602        self.generate_expression(&e.expression)?;
35603        self.write(")");
35604        Ok(())
35605    }
35606
35607    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
35608        let name = if matches!(
35609            self.config.dialect,
35610            Some(crate::dialects::DialectType::Spark)
35611                | Some(crate::dialects::DialectType::Databricks)
35612        ) {
35613            "TRY_MULTIPLY"
35614        } else {
35615            "SAFE_MULTIPLY"
35616        };
35617        self.write_keyword(name);
35618        self.write("(");
35619        self.generate_expression(&e.this)?;
35620        self.write(", ");
35621        self.generate_expression(&e.expression)?;
35622        self.write(")");
35623        Ok(())
35624    }
35625
35626    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
35627        let name = if matches!(
35628            self.config.dialect,
35629            Some(crate::dialects::DialectType::Spark)
35630                | Some(crate::dialects::DialectType::Databricks)
35631        ) {
35632            "TRY_SUBTRACT"
35633        } else {
35634            "SAFE_SUBTRACT"
35635        };
35636        self.write_keyword(name);
35637        self.write("(");
35638        self.generate_expression(&e.this)?;
35639        self.write(", ");
35640        self.generate_expression(&e.expression)?;
35641        self.write(")");
35642        Ok(())
35643    }
35644
35645    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
35646    /// METHOD (size UNIT) [REPEATABLE (seed)]
35647    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
35648        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
35649        if matches!(sample.method, SampleMethod::Bucket) {
35650            self.write(" (");
35651            self.write_keyword("BUCKET");
35652            self.write_space();
35653            if let Some(ref num) = sample.bucket_numerator {
35654                self.generate_expression(num)?;
35655            }
35656            self.write_space();
35657            self.write_keyword("OUT OF");
35658            self.write_space();
35659            if let Some(ref denom) = sample.bucket_denominator {
35660                self.generate_expression(denom)?;
35661            }
35662            if let Some(ref field) = sample.bucket_field {
35663                self.write_space();
35664                self.write_keyword("ON");
35665                self.write_space();
35666                self.generate_expression(field)?;
35667            }
35668            self.write(")");
35669            return Ok(());
35670        }
35671
35672        // Output method name if explicitly specified, or for dialects that always require it
35673        let is_snowflake = matches!(
35674            self.config.dialect,
35675            Some(crate::dialects::DialectType::Snowflake)
35676        );
35677        let is_postgres = matches!(
35678            self.config.dialect,
35679            Some(crate::dialects::DialectType::PostgreSQL)
35680                | Some(crate::dialects::DialectType::Redshift)
35681        );
35682        // Databricks and Spark don't output method names
35683        let is_databricks = matches!(
35684            self.config.dialect,
35685            Some(crate::dialects::DialectType::Databricks)
35686        );
35687        let is_spark = matches!(
35688            self.config.dialect,
35689            Some(crate::dialects::DialectType::Spark)
35690        );
35691        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
35692        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
35693        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
35694        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
35695            self.write_space();
35696            if !sample.explicit_method && (is_snowflake || force_method) {
35697                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
35698                self.write_keyword("BERNOULLI");
35699            } else {
35700                match sample.method {
35701                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
35702                    SampleMethod::System => self.write_keyword("SYSTEM"),
35703                    SampleMethod::Block => self.write_keyword("BLOCK"),
35704                    SampleMethod::Row => self.write_keyword("ROW"),
35705                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
35706                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
35707                    SampleMethod::Bucket => {} // handled above
35708                }
35709            }
35710        }
35711
35712        // Output size, with or without parentheses depending on dialect
35713        let emit_size_no_parens = !self.config.tablesample_requires_parens;
35714        if emit_size_no_parens {
35715            self.write_space();
35716            match &sample.size {
35717                Expression::Tuple(tuple) => {
35718                    for (i, expr) in tuple.expressions.iter().enumerate() {
35719                        if i > 0 {
35720                            self.write(", ");
35721                        }
35722                        self.generate_expression(expr)?;
35723                    }
35724                }
35725                expr => self.generate_expression(expr)?,
35726            }
35727        } else {
35728            self.write(" (");
35729            self.generate_expression(&sample.size)?;
35730        }
35731
35732        // Determine unit
35733        let is_rows_method = matches!(
35734            sample.method,
35735            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
35736        );
35737        let is_percent = matches!(
35738            sample.method,
35739            SampleMethod::Percent
35740                | SampleMethod::System
35741                | SampleMethod::Bernoulli
35742                | SampleMethod::Block
35743        );
35744
35745        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
35746        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
35747        // For Databricks and Spark, always output PERCENT for percentage samples.
35748        let is_presto = matches!(
35749            self.config.dialect,
35750            Some(crate::dialects::DialectType::Presto)
35751                | Some(crate::dialects::DialectType::Trino)
35752                | Some(crate::dialects::DialectType::Athena)
35753        );
35754        let should_output_unit = if is_databricks || is_spark {
35755            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
35756            is_percent || is_rows_method || sample.unit_after_size
35757        } else if is_snowflake || is_postgres || is_presto {
35758            sample.unit_after_size
35759        } else {
35760            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
35761        };
35762
35763        if should_output_unit {
35764            self.write_space();
35765            if sample.is_percent {
35766                self.write_keyword("PERCENT");
35767            } else if is_rows_method && !sample.unit_after_size {
35768                self.write_keyword("ROWS");
35769            } else if sample.unit_after_size {
35770                match sample.method {
35771                    SampleMethod::Percent
35772                    | SampleMethod::System
35773                    | SampleMethod::Bernoulli
35774                    | SampleMethod::Block => {
35775                        self.write_keyword("PERCENT");
35776                    }
35777                    SampleMethod::Row | SampleMethod::Reservoir => {
35778                        self.write_keyword("ROWS");
35779                    }
35780                    _ => self.write_keyword("ROWS"),
35781                }
35782            } else {
35783                self.write_keyword("PERCENT");
35784            }
35785        }
35786
35787        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
35788            if let Some(ref offset) = sample.offset {
35789                self.write_space();
35790                self.write_keyword("OFFSET");
35791                self.write_space();
35792                self.generate_expression(offset)?;
35793            }
35794        }
35795        if !emit_size_no_parens {
35796            self.write(")");
35797        }
35798
35799        Ok(())
35800    }
35801
35802    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
35803        // SAMPLE this (ClickHouse uses SAMPLE BY)
35804        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
35805            self.write_keyword("SAMPLE BY");
35806        } else {
35807            self.write_keyword("SAMPLE");
35808        }
35809        self.write_space();
35810        self.generate_expression(&e.this)?;
35811        Ok(())
35812    }
35813
35814    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
35815        // this (expressions...)
35816        if let Some(this) = &e.this {
35817            self.generate_expression(this)?;
35818        }
35819        if !e.expressions.is_empty() {
35820            // Add space before column list if there's a preceding expression
35821            if e.this.is_some() {
35822                self.write_space();
35823            }
35824            self.write("(");
35825            for (i, expr) in e.expressions.iter().enumerate() {
35826                if i > 0 {
35827                    self.write(", ");
35828                }
35829                self.generate_expression(expr)?;
35830            }
35831            self.write(")");
35832        }
35833        Ok(())
35834    }
35835
35836    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
35837        // COMMENT this
35838        self.write_keyword("COMMENT");
35839        self.write_space();
35840        self.generate_expression(&e.this)?;
35841        Ok(())
35842    }
35843
35844    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
35845        // [this::]expression
35846        if let Some(this) = &e.this {
35847            self.generate_expression(this)?;
35848            self.write("::");
35849        }
35850        self.generate_expression(&e.expression)?;
35851        Ok(())
35852    }
35853
35854    fn generate_search(&mut self, e: &Search) -> Result<()> {
35855        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
35856        self.write_keyword("SEARCH");
35857        self.write("(");
35858        self.generate_expression(&e.this)?;
35859        self.write(", ");
35860        self.generate_expression(&e.expression)?;
35861        if let Some(json_scope) = &e.json_scope {
35862            self.write(", ");
35863            self.generate_expression(json_scope)?;
35864        }
35865        if let Some(analyzer) = &e.analyzer {
35866            self.write(", ");
35867            self.generate_expression(analyzer)?;
35868        }
35869        if let Some(analyzer_options) = &e.analyzer_options {
35870            self.write(", ");
35871            self.generate_expression(analyzer_options)?;
35872        }
35873        if let Some(search_mode) = &e.search_mode {
35874            self.write(", ");
35875            self.generate_expression(search_mode)?;
35876        }
35877        self.write(")");
35878        Ok(())
35879    }
35880
35881    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
35882        // SEARCH_IP(this, expression)
35883        self.write_keyword("SEARCH_IP");
35884        self.write("(");
35885        self.generate_expression(&e.this)?;
35886        self.write(", ");
35887        self.generate_expression(&e.expression)?;
35888        self.write(")");
35889        Ok(())
35890    }
35891
35892    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
35893        // SECURITY this
35894        self.write_keyword("SECURITY");
35895        self.write_space();
35896        self.generate_expression(&e.this)?;
35897        Ok(())
35898    }
35899
35900    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
35901        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
35902        self.write("SEMANTIC_VIEW(");
35903
35904        if self.config.pretty {
35905            // Pretty print: each clause on its own line
35906            self.write_newline();
35907            self.indent_level += 1;
35908            self.write_indent();
35909            self.generate_expression(&e.this)?;
35910
35911            if let Some(metrics) = &e.metrics {
35912                self.write_newline();
35913                self.write_indent();
35914                self.write_keyword("METRICS");
35915                self.write_space();
35916                self.generate_semantic_view_tuple(metrics)?;
35917            }
35918            if let Some(dimensions) = &e.dimensions {
35919                self.write_newline();
35920                self.write_indent();
35921                self.write_keyword("DIMENSIONS");
35922                self.write_space();
35923                self.generate_semantic_view_tuple(dimensions)?;
35924            }
35925            if let Some(facts) = &e.facts {
35926                self.write_newline();
35927                self.write_indent();
35928                self.write_keyword("FACTS");
35929                self.write_space();
35930                self.generate_semantic_view_tuple(facts)?;
35931            }
35932            if let Some(where_) = &e.where_ {
35933                self.write_newline();
35934                self.write_indent();
35935                self.write_keyword("WHERE");
35936                self.write_space();
35937                self.generate_expression(where_)?;
35938            }
35939            self.write_newline();
35940            self.indent_level -= 1;
35941            self.write_indent();
35942        } else {
35943            // Compact: all on one line
35944            self.generate_expression(&e.this)?;
35945            if let Some(metrics) = &e.metrics {
35946                self.write_space();
35947                self.write_keyword("METRICS");
35948                self.write_space();
35949                self.generate_semantic_view_tuple(metrics)?;
35950            }
35951            if let Some(dimensions) = &e.dimensions {
35952                self.write_space();
35953                self.write_keyword("DIMENSIONS");
35954                self.write_space();
35955                self.generate_semantic_view_tuple(dimensions)?;
35956            }
35957            if let Some(facts) = &e.facts {
35958                self.write_space();
35959                self.write_keyword("FACTS");
35960                self.write_space();
35961                self.generate_semantic_view_tuple(facts)?;
35962            }
35963            if let Some(where_) = &e.where_ {
35964                self.write_space();
35965                self.write_keyword("WHERE");
35966                self.write_space();
35967                self.generate_expression(where_)?;
35968            }
35969        }
35970        self.write(")");
35971        Ok(())
35972    }
35973
35974    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
35975    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
35976        if let Expression::Tuple(t) = expr {
35977            for (i, e) in t.expressions.iter().enumerate() {
35978                if i > 0 {
35979                    self.write(", ");
35980                }
35981                self.generate_expression(e)?;
35982            }
35983        } else {
35984            self.generate_expression(expr)?;
35985        }
35986        Ok(())
35987    }
35988
35989    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
35990        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
35991        if let Some(start) = &e.start {
35992            self.write_keyword("START WITH");
35993            self.write_space();
35994            self.generate_expression(start)?;
35995        }
35996        if let Some(increment) = &e.increment {
35997            self.write_space();
35998            self.write_keyword("INCREMENT BY");
35999            self.write_space();
36000            self.generate_expression(increment)?;
36001        }
36002        if let Some(minvalue) = &e.minvalue {
36003            self.write_space();
36004            self.write_keyword("MINVALUE");
36005            self.write_space();
36006            self.generate_expression(minvalue)?;
36007        }
36008        if let Some(maxvalue) = &e.maxvalue {
36009            self.write_space();
36010            self.write_keyword("MAXVALUE");
36011            self.write_space();
36012            self.generate_expression(maxvalue)?;
36013        }
36014        if let Some(cache) = &e.cache {
36015            self.write_space();
36016            self.write_keyword("CACHE");
36017            self.write_space();
36018            self.generate_expression(cache)?;
36019        }
36020        if let Some(owned) = &e.owned {
36021            self.write_space();
36022            self.write_keyword("OWNED BY");
36023            self.write_space();
36024            self.generate_expression(owned)?;
36025        }
36026        for opt in &e.options {
36027            self.write_space();
36028            self.generate_expression(opt)?;
36029        }
36030        Ok(())
36031    }
36032
36033    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
36034        // [WITH] SERDEPROPERTIES (expressions)
36035        if e.with_.is_some() {
36036            self.write_keyword("WITH");
36037            self.write_space();
36038        }
36039        self.write_keyword("SERDEPROPERTIES");
36040        self.write(" (");
36041        for (i, expr) in e.expressions.iter().enumerate() {
36042            if i > 0 {
36043                self.write(", ");
36044            }
36045            // Generate key=value without spaces around =
36046            match expr {
36047                Expression::Eq(eq) => {
36048                    self.generate_expression(&eq.left)?;
36049                    self.write("=");
36050                    self.generate_expression(&eq.right)?;
36051                }
36052                _ => self.generate_expression(expr)?,
36053            }
36054        }
36055        self.write(")");
36056        Ok(())
36057    }
36058
36059    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
36060        // @@[kind.]this
36061        self.write("@@");
36062        if let Some(kind) = &e.kind {
36063            self.write(kind);
36064            self.write(".");
36065        }
36066        self.generate_expression(&e.this)?;
36067        Ok(())
36068    }
36069
36070    fn generate_set(&mut self, e: &Set) -> Result<()> {
36071        // SET/UNSET [TAG] expressions
36072        if e.unset.is_some() {
36073            self.write_keyword("UNSET");
36074        } else {
36075            self.write_keyword("SET");
36076        }
36077        if e.tag.is_some() {
36078            self.write_space();
36079            self.write_keyword("TAG");
36080        }
36081        if !e.expressions.is_empty() {
36082            self.write_space();
36083            for (i, expr) in e.expressions.iter().enumerate() {
36084                if i > 0 {
36085                    self.write(", ");
36086                }
36087                self.generate_expression(expr)?;
36088            }
36089        }
36090        Ok(())
36091    }
36092
36093    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
36094        // SET this or SETCONFIG this
36095        self.write_keyword("SET");
36096        self.write_space();
36097        self.generate_expression(&e.this)?;
36098        Ok(())
36099    }
36100
36101    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
36102        // [kind] name = value
36103        if let Some(kind) = &e.kind {
36104            self.write_keyword(kind);
36105            self.write_space();
36106        }
36107        self.generate_expression(&e.name)?;
36108        self.write(" = ");
36109        self.generate_expression(&e.value)?;
36110        Ok(())
36111    }
36112
36113    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
36114        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
36115        if let Some(with_) = &e.with_ {
36116            self.generate_expression(with_)?;
36117            self.write_space();
36118        }
36119        self.generate_expression(&e.this)?;
36120        self.write_space();
36121        // kind should be UNION, INTERSECT, EXCEPT, etc.
36122        if let Some(kind) = &e.kind {
36123            self.write_keyword(kind);
36124        }
36125        if e.distinct {
36126            self.write_space();
36127            self.write_keyword("DISTINCT");
36128        } else {
36129            self.write_space();
36130            self.write_keyword("ALL");
36131        }
36132        if e.by_name.is_some() {
36133            self.write_space();
36134            self.write_keyword("BY NAME");
36135        }
36136        self.write_space();
36137        self.generate_expression(&e.expression)?;
36138        Ok(())
36139    }
36140
36141    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
36142        // SET or MULTISET
36143        if e.multi.is_some() {
36144            self.write_keyword("MULTISET");
36145        } else {
36146            self.write_keyword("SET");
36147        }
36148        Ok(())
36149    }
36150
36151    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
36152        // SETTINGS expressions
36153        self.write_keyword("SETTINGS");
36154        if self.config.pretty && e.expressions.len() > 1 {
36155            // Pretty print: each setting on its own line, indented
36156            self.indent_level += 1;
36157            for (i, expr) in e.expressions.iter().enumerate() {
36158                if i > 0 {
36159                    self.write(",");
36160                }
36161                self.write_newline();
36162                self.write_indent();
36163                self.generate_expression(expr)?;
36164            }
36165            self.indent_level -= 1;
36166        } else {
36167            self.write_space();
36168            for (i, expr) in e.expressions.iter().enumerate() {
36169                if i > 0 {
36170                    self.write(", ");
36171                }
36172                self.generate_expression(expr)?;
36173            }
36174        }
36175        Ok(())
36176    }
36177
36178    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
36179        // SHARING = this
36180        self.write_keyword("SHARING");
36181        if let Some(this) = &e.this {
36182            self.write(" = ");
36183            self.generate_expression(this)?;
36184        }
36185        Ok(())
36186    }
36187
36188    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
36189        // Python array slicing: begin:end:step
36190        if let Some(begin) = &e.this {
36191            self.generate_expression(begin)?;
36192        }
36193        self.write(":");
36194        if let Some(end) = &e.expression {
36195            self.generate_expression(end)?;
36196        }
36197        if let Some(step) = &e.step {
36198            self.write(":");
36199            self.generate_expression(step)?;
36200        }
36201        Ok(())
36202    }
36203
36204    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
36205        // SORT_ARRAY(this, asc)
36206        self.write_keyword("SORT_ARRAY");
36207        self.write("(");
36208        self.generate_expression(&e.this)?;
36209        if let Some(asc) = &e.asc {
36210            self.write(", ");
36211            self.generate_expression(asc)?;
36212        }
36213        self.write(")");
36214        Ok(())
36215    }
36216
36217    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
36218        // SORT BY expressions
36219        self.write_keyword("SORT BY");
36220        self.write_space();
36221        for (i, expr) in e.expressions.iter().enumerate() {
36222            if i > 0 {
36223                self.write(", ");
36224            }
36225            self.generate_ordered(expr)?;
36226        }
36227        Ok(())
36228    }
36229
36230    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
36231        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
36232        if e.compound.is_some() {
36233            self.write_keyword("COMPOUND");
36234            self.write_space();
36235        }
36236        self.write_keyword("SORTKEY");
36237        self.write("(");
36238        // If this is a Tuple, unwrap its contents to avoid double parentheses
36239        if let Expression::Tuple(t) = e.this.as_ref() {
36240            for (i, expr) in t.expressions.iter().enumerate() {
36241                if i > 0 {
36242                    self.write(", ");
36243                }
36244                self.generate_expression(expr)?;
36245            }
36246        } else {
36247            self.generate_expression(&e.this)?;
36248        }
36249        self.write(")");
36250        Ok(())
36251    }
36252
36253    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
36254        // SPLIT_PART(this, delimiter, part_index)
36255        self.write_keyword("SPLIT_PART");
36256        self.write("(");
36257        self.generate_expression(&e.this)?;
36258        if let Some(delimiter) = &e.delimiter {
36259            self.write(", ");
36260            self.generate_expression(delimiter)?;
36261        }
36262        if let Some(part_index) = &e.part_index {
36263            self.write(", ");
36264            self.generate_expression(part_index)?;
36265        }
36266        self.write(")");
36267        Ok(())
36268    }
36269
36270    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
36271        // READS SQL DATA or MODIFIES SQL DATA, etc.
36272        self.generate_expression(&e.this)?;
36273        Ok(())
36274    }
36275
36276    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
36277        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
36278        self.write_keyword("SQL SECURITY");
36279        self.write_space();
36280        self.generate_expression(&e.this)?;
36281        Ok(())
36282    }
36283
36284    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
36285        // ST_DISTANCE(this, expression, [use_spheroid])
36286        self.write_keyword("ST_DISTANCE");
36287        self.write("(");
36288        self.generate_expression(&e.this)?;
36289        self.write(", ");
36290        self.generate_expression(&e.expression)?;
36291        if let Some(use_spheroid) = &e.use_spheroid {
36292            self.write(", ");
36293            self.generate_expression(use_spheroid)?;
36294        }
36295        self.write(")");
36296        Ok(())
36297    }
36298
36299    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
36300        // ST_POINT(this, expression)
36301        self.write_keyword("ST_POINT");
36302        self.write("(");
36303        self.generate_expression(&e.this)?;
36304        self.write(", ");
36305        self.generate_expression(&e.expression)?;
36306        self.write(")");
36307        Ok(())
36308    }
36309
36310    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
36311        // IMMUTABLE, STABLE, VOLATILE
36312        self.generate_expression(&e.this)?;
36313        Ok(())
36314    }
36315
36316    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
36317        // STANDARD_HASH(this, [expression])
36318        self.write_keyword("STANDARD_HASH");
36319        self.write("(");
36320        self.generate_expression(&e.this)?;
36321        if let Some(expression) = &e.expression {
36322            self.write(", ");
36323            self.generate_expression(expression)?;
36324        }
36325        self.write(")");
36326        Ok(())
36327    }
36328
36329    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
36330        // STORED BY this
36331        self.write_keyword("STORED BY");
36332        self.write_space();
36333        self.generate_expression(&e.this)?;
36334        Ok(())
36335    }
36336
36337    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
36338        // STRPOS(this, substr) or STRPOS(this, substr, position)
36339        // Different dialects have different function names
36340        use crate::dialects::DialectType;
36341        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36342            // Snowflake: CHARINDEX(substr, str[, position])
36343            self.write_keyword("CHARINDEX");
36344            self.write("(");
36345            if let Some(substr) = &e.substr {
36346                self.generate_expression(substr)?;
36347                self.write(", ");
36348            }
36349            self.generate_expression(&e.this)?;
36350            if let Some(position) = &e.position {
36351                self.write(", ");
36352                self.generate_expression(position)?;
36353            }
36354            self.write(")");
36355        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
36356            self.write_keyword("POSITION");
36357            self.write("(");
36358            self.generate_expression(&e.this)?;
36359            if let Some(substr) = &e.substr {
36360                self.write(", ");
36361                self.generate_expression(substr)?;
36362            }
36363            if let Some(position) = &e.position {
36364                self.write(", ");
36365                self.generate_expression(position)?;
36366            }
36367            if let Some(occurrence) = &e.occurrence {
36368                self.write(", ");
36369                self.generate_expression(occurrence)?;
36370            }
36371            self.write(")");
36372        } else if matches!(
36373            self.config.dialect,
36374            Some(DialectType::SQLite)
36375                | Some(DialectType::Oracle)
36376                | Some(DialectType::BigQuery)
36377                | Some(DialectType::Teradata)
36378        ) {
36379            self.write_keyword("INSTR");
36380            self.write("(");
36381            self.generate_expression(&e.this)?;
36382            if let Some(substr) = &e.substr {
36383                self.write(", ");
36384                self.generate_expression(substr)?;
36385            }
36386            if let Some(position) = &e.position {
36387                self.write(", ");
36388                self.generate_expression(position)?;
36389            } else if e.occurrence.is_some() {
36390                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
36391                // Default start position is 1
36392                self.write(", 1");
36393            }
36394            if let Some(occurrence) = &e.occurrence {
36395                self.write(", ");
36396                self.generate_expression(occurrence)?;
36397            }
36398            self.write(")");
36399        } else if matches!(
36400            self.config.dialect,
36401            Some(DialectType::MySQL)
36402                | Some(DialectType::SingleStore)
36403                | Some(DialectType::Doris)
36404                | Some(DialectType::StarRocks)
36405                | Some(DialectType::Hive)
36406                | Some(DialectType::Spark)
36407                | Some(DialectType::Databricks)
36408        ) {
36409            // LOCATE(substr, str[, position]) - substr first
36410            self.write_keyword("LOCATE");
36411            self.write("(");
36412            if let Some(substr) = &e.substr {
36413                self.generate_expression(substr)?;
36414                self.write(", ");
36415            }
36416            self.generate_expression(&e.this)?;
36417            if let Some(position) = &e.position {
36418                self.write(", ");
36419                self.generate_expression(position)?;
36420            }
36421            self.write(")");
36422        } else if matches!(
36423            self.config.dialect,
36424            Some(DialectType::TSQL) | Some(DialectType::Fabric)
36425        ) {
36426            // CHARINDEX(substr, str[, position])
36427            self.write_keyword("CHARINDEX");
36428            self.write("(");
36429            if let Some(substr) = &e.substr {
36430                self.generate_expression(substr)?;
36431                self.write(", ");
36432            }
36433            self.generate_expression(&e.this)?;
36434            if let Some(position) = &e.position {
36435                self.write(", ");
36436                self.generate_expression(position)?;
36437            }
36438            self.write(")");
36439        } else if matches!(
36440            self.config.dialect,
36441            Some(DialectType::PostgreSQL)
36442                | Some(DialectType::Materialize)
36443                | Some(DialectType::RisingWave)
36444                | Some(DialectType::Redshift)
36445        ) {
36446            // POSITION(substr IN str) syntax
36447            self.write_keyword("POSITION");
36448            self.write("(");
36449            if let Some(substr) = &e.substr {
36450                self.generate_expression(substr)?;
36451                self.write(" IN ");
36452            }
36453            self.generate_expression(&e.this)?;
36454            self.write(")");
36455        } else {
36456            self.write_keyword("STRPOS");
36457            self.write("(");
36458            self.generate_expression(&e.this)?;
36459            if let Some(substr) = &e.substr {
36460                self.write(", ");
36461                self.generate_expression(substr)?;
36462            }
36463            if let Some(position) = &e.position {
36464                self.write(", ");
36465                self.generate_expression(position)?;
36466            }
36467            if let Some(occurrence) = &e.occurrence {
36468                self.write(", ");
36469                self.generate_expression(occurrence)?;
36470            }
36471            self.write(")");
36472        }
36473        Ok(())
36474    }
36475
36476    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
36477        match self.config.dialect {
36478            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
36479                self.generate_tsql_str_to_temporal(&e.this, e.format.as_deref(), "DATE")?;
36480            }
36481            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
36482                // TO_DATE(this, java_format)
36483                self.write_keyword("TO_DATE");
36484                self.write("(");
36485                self.generate_expression(&e.this)?;
36486                if let Some(format) = &e.format {
36487                    self.write(", '");
36488                    self.write(&Self::strftime_to_java_format(format));
36489                    self.write("'");
36490                }
36491                self.write(")");
36492            }
36493            Some(DialectType::DuckDB) => {
36494                // CAST(STRPTIME(this, format) AS DATE)
36495                self.write_keyword("CAST");
36496                self.write("(");
36497                self.write_keyword("STRPTIME");
36498                self.write("(");
36499                self.generate_expression(&e.this)?;
36500                if let Some(format) = &e.format {
36501                    self.write(", '");
36502                    self.write(format);
36503                    self.write("'");
36504                }
36505                self.write(")");
36506                self.write_keyword(" AS ");
36507                self.write_keyword("DATE");
36508                self.write(")");
36509            }
36510            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
36511                // TO_DATE(this, pg_format)
36512                self.write_keyword("TO_DATE");
36513                self.write("(");
36514                self.generate_expression(&e.this)?;
36515                if let Some(format) = &e.format {
36516                    self.write(", '");
36517                    self.write(&Self::strftime_to_postgres_format(format));
36518                    self.write("'");
36519                }
36520                self.write(")");
36521            }
36522            Some(DialectType::BigQuery) => {
36523                // PARSE_DATE(format, this) - note: format comes first for BigQuery
36524                self.write_keyword("PARSE_DATE");
36525                self.write("(");
36526                if let Some(format) = &e.format {
36527                    self.write("'");
36528                    self.write(format);
36529                    self.write("'");
36530                    self.write(", ");
36531                }
36532                self.generate_expression(&e.this)?;
36533                self.write(")");
36534            }
36535            Some(DialectType::Teradata) => {
36536                // CAST(this AS DATE FORMAT 'teradata_fmt')
36537                self.write_keyword("CAST");
36538                self.write("(");
36539                self.generate_expression(&e.this)?;
36540                self.write_keyword(" AS ");
36541                self.write_keyword("DATE");
36542                if let Some(format) = &e.format {
36543                    self.write_keyword(" FORMAT ");
36544                    self.write("'");
36545                    self.write(&Self::strftime_to_teradata_format(format));
36546                    self.write("'");
36547                }
36548                self.write(")");
36549            }
36550            _ => {
36551                // STR_TO_DATE(this, format) - MySQL default
36552                self.write_keyword("STR_TO_DATE");
36553                self.write("(");
36554                self.generate_expression(&e.this)?;
36555                if let Some(format) = &e.format {
36556                    self.write(", '");
36557                    self.write(format);
36558                    self.write("'");
36559                }
36560                self.write(")");
36561            }
36562        }
36563        Ok(())
36564    }
36565
36566    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
36567    fn strftime_to_teradata_format(fmt: &str) -> String {
36568        let mut result = String::with_capacity(fmt.len() * 2);
36569        let bytes = fmt.as_bytes();
36570        let len = bytes.len();
36571        let mut i = 0;
36572        while i < len {
36573            if bytes[i] == b'%' && i + 1 < len {
36574                let replacement = match bytes[i + 1] {
36575                    b'Y' => "YYYY",
36576                    b'y' => "YY",
36577                    b'm' => "MM",
36578                    b'B' => "MMMM",
36579                    b'b' => "MMM",
36580                    b'd' => "DD",
36581                    b'j' => "DDD",
36582                    b'H' => "HH",
36583                    b'M' => "MI",
36584                    b'S' => "SS",
36585                    b'f' => "SSSSSS",
36586                    b'A' => "EEEE",
36587                    b'a' => "EEE",
36588                    _ => {
36589                        result.push('%');
36590                        i += 1;
36591                        continue;
36592                    }
36593                };
36594                result.push_str(replacement);
36595                i += 2;
36596            } else {
36597                result.push(bytes[i] as char);
36598                i += 1;
36599            }
36600        }
36601        result
36602    }
36603
36604    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
36605    /// Public static version for use by other modules
36606    pub fn strftime_to_java_format_static(fmt: &str) -> String {
36607        Self::strftime_to_java_format(fmt)
36608    }
36609
36610    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
36611    fn strftime_to_java_format(fmt: &str) -> String {
36612        let mut result = String::with_capacity(fmt.len() * 2);
36613        let bytes = fmt.as_bytes();
36614        let len = bytes.len();
36615        let mut i = 0;
36616        while i < len {
36617            if bytes[i] == b'%' && i + 1 < len {
36618                // Check for non-padded variants (%-X)
36619                if bytes[i + 1] == b'-' && i + 2 < len {
36620                    let replacement = match bytes[i + 2] {
36621                        b'd' => "d",
36622                        b'm' => "M",
36623                        b'H' => "H",
36624                        b'M' => "m",
36625                        b'S' => "s",
36626                        _ => {
36627                            result.push('%');
36628                            i += 1;
36629                            continue;
36630                        }
36631                    };
36632                    result.push_str(replacement);
36633                    i += 3;
36634                } else {
36635                    let replacement = match bytes[i + 1] {
36636                        b'Y' => "yyyy",
36637                        b'y' => "yy",
36638                        b'm' => "MM",
36639                        b'B' => "MMMM",
36640                        b'b' => "MMM",
36641                        b'd' => "dd",
36642                        b'j' => "DDD",
36643                        b'H' => "HH",
36644                        b'M' => "mm",
36645                        b'S' => "ss",
36646                        b'f' => "SSSSSS",
36647                        b'A' => "EEEE",
36648                        b'a' => "EEE",
36649                        _ => {
36650                            result.push('%');
36651                            i += 1;
36652                            continue;
36653                        }
36654                    };
36655                    result.push_str(replacement);
36656                    i += 2;
36657                }
36658            } else {
36659                result.push(bytes[i] as char);
36660                i += 1;
36661            }
36662        }
36663        result
36664    }
36665
36666    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
36667    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
36668    fn strftime_to_tsql_format(fmt: &str) -> String {
36669        let mut result = String::with_capacity(fmt.len() * 2);
36670        let bytes = fmt.as_bytes();
36671        let len = bytes.len();
36672        let mut i = 0;
36673        while i < len {
36674            if bytes[i] == b'%' && i + 1 < len {
36675                // Check for non-padded variants (%-X)
36676                if bytes[i + 1] == b'-' && i + 2 < len {
36677                    let replacement = match bytes[i + 2] {
36678                        b'd' => "d",
36679                        b'm' => "M",
36680                        b'H' => "H",
36681                        b'M' => "m",
36682                        b'S' => "s",
36683                        _ => {
36684                            result.push('%');
36685                            i += 1;
36686                            continue;
36687                        }
36688                    };
36689                    result.push_str(replacement);
36690                    i += 3;
36691                } else {
36692                    let replacement = match bytes[i + 1] {
36693                        b'Y' => "yyyy",
36694                        b'y' => "yy",
36695                        b'm' => "MM",
36696                        b'B' => "MMMM",
36697                        b'b' => "MMM",
36698                        b'd' => "dd",
36699                        b'j' => "DDD",
36700                        b'H' => "HH",
36701                        b'M' => "mm",
36702                        b'S' => "ss",
36703                        b'f' => "ffffff",
36704                        b'A' => "dddd",
36705                        b'a' => "ddd",
36706                        _ => {
36707                            result.push('%');
36708                            i += 1;
36709                            continue;
36710                        }
36711                    };
36712                    result.push_str(replacement);
36713                    i += 2;
36714                }
36715            } else {
36716                result.push(bytes[i] as char);
36717                i += 1;
36718            }
36719        }
36720        result
36721    }
36722
36723    fn tsql_convert_style_for_strftime(fmt: &str) -> Option<u16> {
36724        match fmt.trim() {
36725            "%b %d %Y %-I:%M%p" => Some(100),
36726            "%m/%d/%y" => Some(1),
36727            "%y.%m.%d" => Some(2),
36728            "%d/%m/%y" => Some(3),
36729            "%d.%m.%y" => Some(4),
36730            "%d-%m-%y" => Some(5),
36731            "%d %b %y" => Some(6),
36732            "%b %d, %y" => Some(7),
36733            "%H:%M:%S" => Some(108),
36734            "%b %d %Y %-I:%M:%S:%f%p" => Some(109),
36735            "%m-%d-%y" => Some(10),
36736            "%y/%m/%d" => Some(11),
36737            "%y%m%d" => Some(12),
36738            "%d %b %Y %H:%M:%S:%f" => Some(113),
36739            "%H:%M:%S:%f" => Some(114),
36740            "%m/%d/%Y" => Some(101),
36741            "%Y.%m.%d" => Some(102),
36742            "%d/%m/%Y" => Some(103),
36743            "%d.%m.%Y" => Some(104),
36744            "%d-%m-%Y" => Some(105),
36745            "%d %b %Y" => Some(106),
36746            "%b %d, %Y" => Some(107),
36747            "%m-%d-%Y" => Some(110),
36748            "%Y/%m/%d" => Some(111),
36749            "%Y%m%d" => Some(112),
36750            "%Y-%m-%d %H:%M:%S" => Some(120),
36751            "%Y-%m-%d %H:%M:%S.%f" => Some(121),
36752            "%Y-%m-%dT%H:%M:%S" | "%Y-%m-%dT%H:%M:%S.%f" => Some(126),
36753            "%Y-%m-%d" => Some(23),
36754            _ => None,
36755        }
36756    }
36757
36758    fn generate_tsql_str_to_temporal(
36759        &mut self,
36760        this: &Expression,
36761        format: Option<&str>,
36762        target_type: &str,
36763    ) -> Result<()> {
36764        if let Some(format) = format {
36765            if let Some(style) = Self::tsql_convert_style_for_strftime(format) {
36766                self.write_keyword("CONVERT");
36767                self.write("(");
36768                self.write_keyword(target_type);
36769                self.write(", ");
36770                self.generate_expression(this)?;
36771                self.write(", ");
36772                self.write(&style.to_string());
36773                self.write(")");
36774                return Ok(());
36775            }
36776
36777            self.unsupported(format!(
36778                "T-SQL/Fabric {target_type} parsing format '{format}' has no CONVERT style mapping"
36779            ))?;
36780        }
36781
36782        self.write_keyword("CAST");
36783        self.write("(");
36784        self.generate_expression(this)?;
36785        self.write(" AS ");
36786        self.write_keyword(target_type);
36787        self.write(")");
36788        Ok(())
36789    }
36790
36791    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
36792    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
36793    fn decompose_json_path(path: &str) -> Vec<String> {
36794        let mut parts = Vec::new();
36795        // Strip leading $ and optional .
36796        let path = if path.starts_with("$.") {
36797            &path[2..]
36798        } else if path.starts_with('$') {
36799            &path[1..]
36800        } else {
36801            path
36802        };
36803        if path.is_empty() {
36804            return parts;
36805        }
36806        let mut current = String::new();
36807        let chars: Vec<char> = path.chars().collect();
36808        let mut i = 0;
36809        while i < chars.len() {
36810            match chars[i] {
36811                '.' => {
36812                    if !current.is_empty() {
36813                        parts.push(current.clone());
36814                        current.clear();
36815                    }
36816                    i += 1;
36817                }
36818                '[' => {
36819                    if !current.is_empty() {
36820                        parts.push(current.clone());
36821                        current.clear();
36822                    }
36823                    i += 1;
36824                    // Read the content inside brackets
36825                    let mut bracket_content = String::new();
36826                    while i < chars.len() && chars[i] != ']' {
36827                        // Skip quotes inside brackets
36828                        if chars[i] == '"' || chars[i] == '\'' {
36829                            let quote = chars[i];
36830                            i += 1;
36831                            while i < chars.len() && chars[i] != quote {
36832                                bracket_content.push(chars[i]);
36833                                i += 1;
36834                            }
36835                            if i < chars.len() {
36836                                i += 1;
36837                            } // skip closing quote
36838                        } else {
36839                            bracket_content.push(chars[i]);
36840                            i += 1;
36841                        }
36842                    }
36843                    if i < chars.len() {
36844                        i += 1;
36845                    } // skip ]
36846                      // Skip wildcard [*] - don't add as a part
36847                    if bracket_content != "*" {
36848                        parts.push(bracket_content);
36849                    }
36850                }
36851                _ => {
36852                    current.push(chars[i]);
36853                    i += 1;
36854                }
36855            }
36856        }
36857        if !current.is_empty() {
36858            parts.push(current);
36859        }
36860        parts
36861    }
36862
36863    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
36864    fn strftime_to_postgres_format(fmt: &str) -> String {
36865        let mut result = String::with_capacity(fmt.len() * 2);
36866        let bytes = fmt.as_bytes();
36867        let len = bytes.len();
36868        let mut i = 0;
36869        while i < len {
36870            if bytes[i] == b'%' && i + 1 < len {
36871                // Check for non-padded variants (%-X)
36872                if bytes[i + 1] == b'-' && i + 2 < len {
36873                    let replacement = match bytes[i + 2] {
36874                        b'd' => "FMDD",
36875                        b'm' => "FMMM",
36876                        b'H' => "FMHH24",
36877                        b'M' => "FMMI",
36878                        b'S' => "FMSS",
36879                        _ => {
36880                            result.push('%');
36881                            i += 1;
36882                            continue;
36883                        }
36884                    };
36885                    result.push_str(replacement);
36886                    i += 3;
36887                } else {
36888                    let replacement = match bytes[i + 1] {
36889                        b'Y' => "YYYY",
36890                        b'y' => "YY",
36891                        b'm' => "MM",
36892                        b'B' => "Month",
36893                        b'b' => "Mon",
36894                        b'd' => "DD",
36895                        b'j' => "DDD",
36896                        b'H' => "HH24",
36897                        b'M' => "MI",
36898                        b'S' => "SS",
36899                        b'f' => "US",
36900                        b'A' => "Day",
36901                        b'a' => "Dy",
36902                        _ => {
36903                            result.push('%');
36904                            i += 1;
36905                            continue;
36906                        }
36907                    };
36908                    result.push_str(replacement);
36909                    i += 2;
36910                }
36911            } else {
36912                result.push(bytes[i] as char);
36913                i += 1;
36914            }
36915        }
36916        result
36917    }
36918
36919    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
36920    fn strftime_to_snowflake_format(fmt: &str) -> String {
36921        let mut result = String::with_capacity(fmt.len() * 2);
36922        let bytes = fmt.as_bytes();
36923        let len = bytes.len();
36924        let mut i = 0;
36925        while i < len {
36926            if bytes[i] == b'%' && i + 1 < len {
36927                // Check for non-padded variants (%-X)
36928                if bytes[i + 1] == b'-' && i + 2 < len {
36929                    let replacement = match bytes[i + 2] {
36930                        b'd' => "dd",
36931                        b'm' => "mm",
36932                        _ => {
36933                            result.push('%');
36934                            i += 1;
36935                            continue;
36936                        }
36937                    };
36938                    result.push_str(replacement);
36939                    i += 3;
36940                } else {
36941                    let replacement = match bytes[i + 1] {
36942                        b'Y' => "yyyy",
36943                        b'y' => "yy",
36944                        b'm' => "mm",
36945                        b'd' => "DD",
36946                        b'H' => "hh24",
36947                        b'M' => "mi",
36948                        b'S' => "ss",
36949                        b'f' => "ff",
36950                        _ => {
36951                            result.push('%');
36952                            i += 1;
36953                            continue;
36954                        }
36955                    };
36956                    result.push_str(replacement);
36957                    i += 2;
36958                }
36959            } else {
36960                result.push(bytes[i] as char);
36961                i += 1;
36962            }
36963        }
36964        result
36965    }
36966
36967    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
36968        // STR_TO_MAP(this, pair_delim, key_value_delim)
36969        self.write_keyword("STR_TO_MAP");
36970        self.write("(");
36971        self.generate_expression(&e.this)?;
36972        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
36973        let needs_defaults = matches!(
36974            self.config.dialect,
36975            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
36976        );
36977        if let Some(pair_delim) = &e.pair_delim {
36978            self.write(", ");
36979            self.generate_expression(pair_delim)?;
36980        } else if needs_defaults {
36981            self.write(", ','");
36982        }
36983        if let Some(key_value_delim) = &e.key_value_delim {
36984            self.write(", ");
36985            self.generate_expression(key_value_delim)?;
36986        } else if needs_defaults {
36987            self.write(", ':'");
36988        }
36989        self.write(")");
36990        Ok(())
36991    }
36992
36993    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
36994        // Detect format style: strftime (starts with %) vs Snowflake/Java
36995        let is_strftime = e.format.contains('%');
36996        // Helper: get strftime format from whatever style is stored
36997        let to_strftime = |f: &str| -> String {
36998            if is_strftime {
36999                f.to_string()
37000            } else {
37001                Self::snowflake_format_to_strftime(f)
37002            }
37003        };
37004        // Helper: get Java format
37005        let to_java = |f: &str| -> String {
37006            if is_strftime {
37007                Self::strftime_to_java_format(f)
37008            } else {
37009                Self::snowflake_format_to_spark(f)
37010            }
37011        };
37012        // Helper: get PG format
37013        let to_pg = |f: &str| -> String {
37014            if is_strftime {
37015                Self::strftime_to_postgres_format(f)
37016            } else {
37017                Self::convert_strptime_to_postgres_format(f)
37018            }
37019        };
37020
37021        match self.config.dialect {
37022            Some(DialectType::Exasol) => {
37023                self.write_keyword("TO_DATE");
37024                self.write("(");
37025                self.generate_expression(&e.this)?;
37026                self.write(", '");
37027                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
37028                self.write("'");
37029                self.write(")");
37030            }
37031            Some(DialectType::BigQuery) => {
37032                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
37033                let fmt = to_strftime(&e.format);
37034                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
37035                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
37036                self.write_keyword("PARSE_TIMESTAMP");
37037                self.write("('");
37038                self.write(&fmt);
37039                self.write("', ");
37040                self.generate_expression(&e.this)?;
37041                self.write(")");
37042            }
37043            Some(DialectType::Hive) => {
37044                // Hive: CAST(x AS TIMESTAMP) for simple date formats
37045                // Check both the raw format and the converted format (in case it's already Java)
37046                let java_fmt = to_java(&e.format);
37047                if java_fmt == "yyyy-MM-dd HH:mm:ss"
37048                    || java_fmt == "yyyy-MM-dd"
37049                    || e.format == "yyyy-MM-dd HH:mm:ss"
37050                    || e.format == "yyyy-MM-dd"
37051                {
37052                    self.write_keyword("CAST");
37053                    self.write("(");
37054                    self.generate_expression(&e.this)?;
37055                    self.write(" ");
37056                    self.write_keyword("AS TIMESTAMP");
37057                    self.write(")");
37058                } else {
37059                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
37060                    self.write_keyword("CAST");
37061                    self.write("(");
37062                    self.write_keyword("FROM_UNIXTIME");
37063                    self.write("(");
37064                    self.write_keyword("UNIX_TIMESTAMP");
37065                    self.write("(");
37066                    self.generate_expression(&e.this)?;
37067                    self.write(", '");
37068                    self.write(&java_fmt);
37069                    self.write("')");
37070                    self.write(") ");
37071                    self.write_keyword("AS TIMESTAMP");
37072                    self.write(")");
37073                }
37074            }
37075            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37076                // Spark: TO_TIMESTAMP(value, java_format)
37077                let java_fmt = to_java(&e.format);
37078                self.write_keyword("TO_TIMESTAMP");
37079                self.write("(");
37080                self.generate_expression(&e.this)?;
37081                self.write(", '");
37082                self.write(&java_fmt);
37083                self.write("')");
37084            }
37085            Some(DialectType::MySQL) => {
37086                // MySQL: STR_TO_DATE(value, format)
37087                let mut fmt = to_strftime(&e.format);
37088                // MySQL uses %e for non-padded day, %T for %H:%M:%S
37089                fmt = fmt.replace("%-d", "%e");
37090                fmt = fmt.replace("%-m", "%c");
37091                fmt = fmt.replace("%H:%M:%S", "%T");
37092                self.write_keyword("STR_TO_DATE");
37093                self.write("(");
37094                self.generate_expression(&e.this)?;
37095                self.write(", '");
37096                self.write(&fmt);
37097                self.write("')");
37098            }
37099            Some(DialectType::Drill) => {
37100                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
37101                let java_fmt = to_java(&e.format);
37102                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
37103                let java_fmt = java_fmt.replace('T', "''T''");
37104                self.write_keyword("TO_TIMESTAMP");
37105                self.write("(");
37106                self.generate_expression(&e.this)?;
37107                self.write(", '");
37108                self.write(&java_fmt);
37109                self.write("')");
37110            }
37111            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
37112                // Presto: DATE_PARSE(value, strftime_format)
37113                let mut fmt = to_strftime(&e.format);
37114                // Presto uses %e for non-padded day, %T for %H:%M:%S
37115                fmt = fmt.replace("%-d", "%e");
37116                fmt = fmt.replace("%-m", "%c");
37117                fmt = fmt.replace("%H:%M:%S", "%T");
37118                self.write_keyword("DATE_PARSE");
37119                self.write("(");
37120                self.generate_expression(&e.this)?;
37121                self.write(", '");
37122                self.write(&fmt);
37123                self.write("')");
37124            }
37125            Some(DialectType::DuckDB) => {
37126                // DuckDB: STRPTIME(value, strftime_format)
37127                let fmt = to_strftime(&e.format);
37128                self.write_keyword("STRPTIME");
37129                self.write("(");
37130                self.generate_expression(&e.this)?;
37131                self.write(", '");
37132                self.write(&fmt);
37133                self.write("')");
37134            }
37135            Some(DialectType::PostgreSQL)
37136            | Some(DialectType::Redshift)
37137            | Some(DialectType::Materialize) => {
37138                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
37139                let pg_fmt = to_pg(&e.format);
37140                self.write_keyword("TO_TIMESTAMP");
37141                self.write("(");
37142                self.generate_expression(&e.this)?;
37143                self.write(", '");
37144                self.write(&pg_fmt);
37145                self.write("')");
37146            }
37147            Some(DialectType::Oracle) => {
37148                // Oracle: TO_TIMESTAMP(value, pg_format)
37149                let pg_fmt = to_pg(&e.format);
37150                self.write_keyword("TO_TIMESTAMP");
37151                self.write("(");
37152                self.generate_expression(&e.this)?;
37153                self.write(", '");
37154                self.write(&pg_fmt);
37155                self.write("')");
37156            }
37157            Some(DialectType::Snowflake) => {
37158                // Snowflake: TO_TIMESTAMP(value, format) - native format
37159                self.write_keyword("TO_TIMESTAMP");
37160                self.write("(");
37161                self.generate_expression(&e.this)?;
37162                self.write(", '");
37163                self.write(&e.format);
37164                self.write("')");
37165            }
37166            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
37167                self.generate_tsql_str_to_temporal(&e.this, Some(&e.format), "DATETIME2")?;
37168            }
37169            _ => {
37170                // Default: STR_TO_TIME(this, format)
37171                self.write_keyword("STR_TO_TIME");
37172                self.write("(");
37173                self.generate_expression(&e.this)?;
37174                self.write(", '");
37175                self.write(&e.format);
37176                self.write("'");
37177                self.write(")");
37178            }
37179        }
37180        Ok(())
37181    }
37182
37183    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
37184    fn snowflake_format_to_strftime(format: &str) -> String {
37185        let mut result = String::new();
37186        let chars: Vec<char> = format.chars().collect();
37187        let mut i = 0;
37188        while i < chars.len() {
37189            let remaining = &format[i..];
37190            if remaining.starts_with("yyyy") {
37191                result.push_str("%Y");
37192                i += 4;
37193            } else if remaining.starts_with("yy") {
37194                result.push_str("%y");
37195                i += 2;
37196            } else if remaining.starts_with("mmmm") {
37197                result.push_str("%B"); // full month name
37198                i += 4;
37199            } else if remaining.starts_with("mon") {
37200                result.push_str("%b"); // abbreviated month
37201                i += 3;
37202            } else if remaining.starts_with("mm") {
37203                result.push_str("%m");
37204                i += 2;
37205            } else if remaining.starts_with("DD") {
37206                result.push_str("%d");
37207                i += 2;
37208            } else if remaining.starts_with("dy") {
37209                result.push_str("%a"); // abbreviated day name
37210                i += 2;
37211            } else if remaining.starts_with("hh24") {
37212                result.push_str("%H");
37213                i += 4;
37214            } else if remaining.starts_with("hh12") {
37215                result.push_str("%I");
37216                i += 4;
37217            } else if remaining.starts_with("hh") {
37218                result.push_str("%H");
37219                i += 2;
37220            } else if remaining.starts_with("mi") {
37221                result.push_str("%M");
37222                i += 2;
37223            } else if remaining.starts_with("ss") {
37224                result.push_str("%S");
37225                i += 2;
37226            } else if remaining.starts_with("ff") {
37227                // Fractional seconds
37228                result.push_str("%f");
37229                i += 2;
37230                // Skip digits after ff (ff3, ff6, ff9)
37231                while i < chars.len() && chars[i].is_ascii_digit() {
37232                    i += 1;
37233                }
37234            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
37235                result.push_str("%p");
37236                i += 2;
37237            } else if remaining.starts_with("tz") {
37238                result.push_str("%Z");
37239                i += 2;
37240            } else {
37241                result.push(chars[i]);
37242                i += 1;
37243            }
37244        }
37245        result
37246    }
37247
37248    /// Convert Snowflake normalized format to Spark format (Java-style)
37249    fn snowflake_format_to_spark(format: &str) -> String {
37250        let mut result = String::new();
37251        let chars: Vec<char> = format.chars().collect();
37252        let mut i = 0;
37253        while i < chars.len() {
37254            let remaining = &format[i..];
37255            if remaining.starts_with("yyyy") {
37256                result.push_str("yyyy");
37257                i += 4;
37258            } else if remaining.starts_with("yy") {
37259                result.push_str("yy");
37260                i += 2;
37261            } else if remaining.starts_with("mmmm") {
37262                result.push_str("MMMM"); // full month name
37263                i += 4;
37264            } else if remaining.starts_with("mon") {
37265                result.push_str("MMM"); // abbreviated month
37266                i += 3;
37267            } else if remaining.starts_with("mm") {
37268                result.push_str("MM");
37269                i += 2;
37270            } else if remaining.starts_with("DD") {
37271                result.push_str("dd");
37272                i += 2;
37273            } else if remaining.starts_with("dy") {
37274                result.push_str("EEE"); // abbreviated day name
37275                i += 2;
37276            } else if remaining.starts_with("hh24") {
37277                result.push_str("HH");
37278                i += 4;
37279            } else if remaining.starts_with("hh12") {
37280                result.push_str("hh");
37281                i += 4;
37282            } else if remaining.starts_with("hh") {
37283                result.push_str("HH");
37284                i += 2;
37285            } else if remaining.starts_with("mi") {
37286                result.push_str("mm");
37287                i += 2;
37288            } else if remaining.starts_with("ss") {
37289                result.push_str("ss");
37290                i += 2;
37291            } else if remaining.starts_with("ff") {
37292                result.push_str("SSS"); // milliseconds
37293                i += 2;
37294                // Skip digits after ff
37295                while i < chars.len() && chars[i].is_ascii_digit() {
37296                    i += 1;
37297                }
37298            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
37299                result.push_str("a");
37300                i += 2;
37301            } else if remaining.starts_with("tz") {
37302                result.push_str("z");
37303                i += 2;
37304            } else {
37305                result.push(chars[i]);
37306                i += 1;
37307            }
37308        }
37309        result
37310    }
37311
37312    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
37313        match self.config.dialect {
37314            Some(DialectType::DuckDB) => {
37315                // DuckDB: EPOCH(STRPTIME(value, format))
37316                self.write_keyword("EPOCH");
37317                self.write("(");
37318                self.write_keyword("STRPTIME");
37319                self.write("(");
37320                if let Some(this) = &e.this {
37321                    self.generate_expression(this)?;
37322                }
37323                if let Some(format) = &e.format {
37324                    self.write(", '");
37325                    self.write(format);
37326                    self.write("'");
37327                }
37328                self.write("))");
37329            }
37330            Some(DialectType::Hive) => {
37331                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
37332                self.write_keyword("UNIX_TIMESTAMP");
37333                self.write("(");
37334                if let Some(this) = &e.this {
37335                    self.generate_expression(this)?;
37336                }
37337                if let Some(format) = &e.format {
37338                    let java_fmt = Self::strftime_to_java_format(format);
37339                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
37340                        self.write(", '");
37341                        self.write(&java_fmt);
37342                        self.write("'");
37343                    }
37344                }
37345                self.write(")");
37346            }
37347            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
37348                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
37349                self.write_keyword("UNIX_TIMESTAMP");
37350                self.write("(");
37351                if let Some(this) = &e.this {
37352                    self.generate_expression(this)?;
37353                }
37354                if let Some(format) = &e.format {
37355                    self.write(", '");
37356                    self.write(format);
37357                    self.write("'");
37358                }
37359                self.write(")");
37360            }
37361            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37362                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
37363                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
37364                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
37365                let java_fmt = Self::strftime_to_java_format(c_fmt);
37366                self.write_keyword("TO_UNIXTIME");
37367                self.write("(");
37368                self.write_keyword("COALESCE");
37369                self.write("(");
37370                self.write_keyword("TRY");
37371                self.write("(");
37372                self.write_keyword("DATE_PARSE");
37373                self.write("(");
37374                self.write_keyword("CAST");
37375                self.write("(");
37376                if let Some(this) = &e.this {
37377                    self.generate_expression(this)?;
37378                }
37379                self.write(" ");
37380                self.write_keyword("AS VARCHAR");
37381                self.write("), '");
37382                self.write(c_fmt);
37383                self.write("')), ");
37384                self.write_keyword("PARSE_DATETIME");
37385                self.write("(");
37386                self.write_keyword("DATE_FORMAT");
37387                self.write("(");
37388                self.write_keyword("CAST");
37389                self.write("(");
37390                if let Some(this) = &e.this {
37391                    self.generate_expression(this)?;
37392                }
37393                self.write(" ");
37394                self.write_keyword("AS TIMESTAMP");
37395                self.write("), '");
37396                self.write(c_fmt);
37397                self.write("'), '");
37398                self.write(&java_fmt);
37399                self.write("')))");
37400            }
37401            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37402                // Spark: UNIX_TIMESTAMP(value, java_format)
37403                self.write_keyword("UNIX_TIMESTAMP");
37404                self.write("(");
37405                if let Some(this) = &e.this {
37406                    self.generate_expression(this)?;
37407                }
37408                if let Some(format) = &e.format {
37409                    let java_fmt = Self::strftime_to_java_format(format);
37410                    self.write(", '");
37411                    self.write(&java_fmt);
37412                    self.write("'");
37413                }
37414                self.write(")");
37415            }
37416            _ => {
37417                // Default: STR_TO_UNIX(this, format)
37418                self.write_keyword("STR_TO_UNIX");
37419                self.write("(");
37420                if let Some(this) = &e.this {
37421                    self.generate_expression(this)?;
37422                }
37423                if let Some(format) = &e.format {
37424                    self.write(", '");
37425                    self.write(format);
37426                    self.write("'");
37427                }
37428                self.write(")");
37429            }
37430        }
37431        Ok(())
37432    }
37433
37434    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
37435        // STRING_TO_ARRAY(this, delimiter, null_string)
37436        self.write_keyword("STRING_TO_ARRAY");
37437        self.write("(");
37438        self.generate_expression(&e.this)?;
37439        if let Some(expression) = &e.expression {
37440            self.write(", ");
37441            self.generate_expression(expression)?;
37442        }
37443        if let Some(null_val) = &e.null {
37444            self.write(", ");
37445            self.generate_expression(null_val)?;
37446        }
37447        self.write(")");
37448        Ok(())
37449    }
37450
37451    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
37452        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
37453            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
37454            self.write_keyword("OBJECT_CONSTRUCT");
37455            self.write("(");
37456            for (i, (name, expr)) in e.fields.iter().enumerate() {
37457                if i > 0 {
37458                    self.write(", ");
37459                }
37460                if let Some(name) = name {
37461                    self.write("'");
37462                    self.write(name);
37463                    self.write("'");
37464                    self.write(", ");
37465                } else {
37466                    self.write("'_");
37467                    self.write(&i.to_string());
37468                    self.write("'");
37469                    self.write(", ");
37470                }
37471                self.generate_expression(expr)?;
37472            }
37473            self.write(")");
37474        } else if self.config.struct_curly_brace_notation {
37475            // DuckDB-style: {'key': value, ...}
37476            self.write("{");
37477            for (i, (name, expr)) in e.fields.iter().enumerate() {
37478                if i > 0 {
37479                    self.write(", ");
37480                }
37481                if let Some(name) = name {
37482                    // Quote the key as a string literal
37483                    self.write("'");
37484                    self.write(name);
37485                    self.write("'");
37486                    self.write(": ");
37487                } else {
37488                    // Unnamed field: use positional key
37489                    self.write("'_");
37490                    self.write(&i.to_string());
37491                    self.write("'");
37492                    self.write(": ");
37493                }
37494                self.generate_expression(expr)?;
37495            }
37496            self.write("}");
37497        } else {
37498            // Standard SQL struct notation
37499            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
37500            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
37501            let value_as_name = matches!(
37502                self.config.dialect,
37503                Some(DialectType::BigQuery)
37504                    | Some(DialectType::Spark)
37505                    | Some(DialectType::Databricks)
37506                    | Some(DialectType::Hive)
37507            );
37508            self.write_keyword("STRUCT");
37509            self.write("(");
37510            for (i, (name, expr)) in e.fields.iter().enumerate() {
37511                if i > 0 {
37512                    self.write(", ");
37513                }
37514                if let Some(name) = name {
37515                    if value_as_name {
37516                        // STRUCT(value AS name)
37517                        self.generate_expression(expr)?;
37518                        self.write_space();
37519                        self.write_keyword("AS");
37520                        self.write_space();
37521                        // Quote name if it contains spaces or special chars
37522                        let needs_quoting = name.contains(' ') || name.contains('-');
37523                        if needs_quoting {
37524                            if matches!(
37525                                self.config.dialect,
37526                                Some(DialectType::Spark)
37527                                    | Some(DialectType::Databricks)
37528                                    | Some(DialectType::Hive)
37529                            ) {
37530                                self.write("`");
37531                                self.write(name);
37532                                self.write("`");
37533                            } else {
37534                                self.write(name);
37535                            }
37536                        } else {
37537                            self.write(name);
37538                        }
37539                    } else {
37540                        // STRUCT(name AS value)
37541                        self.write(name);
37542                        self.write_space();
37543                        self.write_keyword("AS");
37544                        self.write_space();
37545                        self.generate_expression(expr)?;
37546                    }
37547                } else {
37548                    self.generate_expression(expr)?;
37549                }
37550            }
37551            self.write(")");
37552        }
37553        Ok(())
37554    }
37555
37556    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
37557        // STUFF(this, start, length, expression)
37558        self.write_keyword("STUFF");
37559        self.write("(");
37560        self.generate_expression(&e.this)?;
37561        if let Some(start) = &e.start {
37562            self.write(", ");
37563            self.generate_expression(start)?;
37564        }
37565        if let Some(length) = e.length {
37566            self.write(", ");
37567            self.write(&length.to_string());
37568        }
37569        self.write(", ");
37570        self.generate_expression(&e.expression)?;
37571        self.write(")");
37572        Ok(())
37573    }
37574
37575    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
37576        // SUBSTRING_INDEX(this, delimiter, count)
37577        self.write_keyword("SUBSTRING_INDEX");
37578        self.write("(");
37579        self.generate_expression(&e.this)?;
37580        if let Some(delimiter) = &e.delimiter {
37581            self.write(", ");
37582            self.generate_expression(delimiter)?;
37583        }
37584        if let Some(count) = &e.count {
37585            self.write(", ");
37586            self.generate_expression(count)?;
37587        }
37588        self.write(")");
37589        Ok(())
37590    }
37591
37592    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
37593        // SUMMARIZE [TABLE] this
37594        self.write_keyword("SUMMARIZE");
37595        if e.table.is_some() {
37596            self.write_space();
37597            self.write_keyword("TABLE");
37598        }
37599        self.write_space();
37600        self.generate_expression(&e.this)?;
37601        Ok(())
37602    }
37603
37604    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
37605        // SYSTIMESTAMP
37606        self.write_keyword("SYSTIMESTAMP");
37607        Ok(())
37608    }
37609
37610    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
37611        // alias (columns...)
37612        if let Some(this) = &e.this {
37613            self.generate_expression(this)?;
37614        }
37615        if !e.columns.is_empty() {
37616            self.write("(");
37617            for (i, col) in e.columns.iter().enumerate() {
37618                if i > 0 {
37619                    self.write(", ");
37620                }
37621                self.generate_expression(col)?;
37622            }
37623            self.write(")");
37624        }
37625        Ok(())
37626    }
37627
37628    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
37629        // TABLE(this) [AS alias]
37630        self.write_keyword("TABLE");
37631        self.write("(");
37632        self.generate_expression(&e.this)?;
37633        self.write(")");
37634        if let Some(alias) = &e.alias {
37635            self.write_space();
37636            self.write_keyword("AS");
37637            self.write_space();
37638            self.write(alias);
37639        }
37640        Ok(())
37641    }
37642
37643    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
37644        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
37645        self.write_keyword("ROWS FROM");
37646        self.write(" (");
37647        for (i, expr) in e.expressions.iter().enumerate() {
37648            if i > 0 {
37649                self.write(", ");
37650            }
37651            // Each expression is either:
37652            // - A plain function (no alias)
37653            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
37654            match expr {
37655                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
37656                    // First element is the function, second is the TableAlias
37657                    self.generate_expression(&tuple.expressions[0])?;
37658                    self.write_space();
37659                    self.write_keyword("AS");
37660                    self.write_space();
37661                    self.generate_expression(&tuple.expressions[1])?;
37662                }
37663                _ => {
37664                    self.generate_expression(expr)?;
37665                }
37666            }
37667        }
37668        self.write(")");
37669        if e.ordinality {
37670            self.write_space();
37671            self.write_keyword("WITH ORDINALITY");
37672        }
37673        if let Some(alias) = &e.alias {
37674            self.write_space();
37675            self.write_keyword("AS");
37676            self.write_space();
37677            self.generate_expression(alias)?;
37678        }
37679        Ok(())
37680    }
37681
37682    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
37683        use crate::dialects::DialectType;
37684
37685        // New wrapper pattern: expression + Sample struct
37686        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
37687            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
37688            if self.config.alias_post_tablesample {
37689                // Handle Subquery with alias and Alias wrapper
37690                if let Expression::Subquery(ref s) = **this {
37691                    if let Some(ref alias) = s.alias {
37692                        // Create a clone without alias for output
37693                        let mut subquery_no_alias = (**s).clone();
37694                        subquery_no_alias.alias = None;
37695                        subquery_no_alias.column_aliases = Vec::new();
37696                        self.generate_expression(&Expression::Subquery(Box::new(
37697                            subquery_no_alias,
37698                        )))?;
37699                        self.write_space();
37700                        self.write_keyword(self.config.tablesample_keywords);
37701                        self.generate_sample_body(sample)?;
37702                        if let Some(ref seed) = sample.seed {
37703                            self.write_space();
37704                            let use_seed = sample.use_seed_keyword
37705                                && !matches!(
37706                                    self.config.dialect,
37707                                    Some(crate::dialects::DialectType::Databricks)
37708                                        | Some(crate::dialects::DialectType::Spark)
37709                                );
37710                            if use_seed {
37711                                self.write_keyword("SEED");
37712                            } else {
37713                                self.write_keyword("REPEATABLE");
37714                            }
37715                            self.write(" (");
37716                            self.generate_expression(seed)?;
37717                            self.write(")");
37718                        }
37719                        self.write_space();
37720                        self.write_keyword("AS");
37721                        self.write_space();
37722                        self.generate_identifier(alias)?;
37723                        return Ok(());
37724                    }
37725                } else if let Expression::Alias(ref a) = **this {
37726                    // Output the base expression without alias
37727                    self.generate_expression(&a.this)?;
37728                    self.write_space();
37729                    self.write_keyword(self.config.tablesample_keywords);
37730                    self.generate_sample_body(sample)?;
37731                    if let Some(ref seed) = sample.seed {
37732                        self.write_space();
37733                        let use_seed = sample.use_seed_keyword
37734                            && !matches!(
37735                                self.config.dialect,
37736                                Some(crate::dialects::DialectType::Databricks)
37737                                    | Some(crate::dialects::DialectType::Spark)
37738                            );
37739                        if use_seed {
37740                            self.write_keyword("SEED");
37741                        } else {
37742                            self.write_keyword("REPEATABLE");
37743                        }
37744                        self.write(" (");
37745                        self.generate_expression(seed)?;
37746                        self.write(")");
37747                    }
37748                    // Output alias after TABLESAMPLE
37749                    self.write_space();
37750                    self.write_keyword("AS");
37751                    self.write_space();
37752                    self.generate_identifier(&a.alias)?;
37753                    return Ok(());
37754                }
37755            }
37756            // Default: generate wrapped expression first, then TABLESAMPLE
37757            self.generate_expression(this)?;
37758            self.write_space();
37759            self.write_keyword(self.config.tablesample_keywords);
37760            self.generate_sample_body(sample)?;
37761            // Seed for table-level sample
37762            if let Some(ref seed) = sample.seed {
37763                self.write_space();
37764                // Databricks uses REPEATABLE, not SEED
37765                let use_seed = sample.use_seed_keyword
37766                    && !matches!(
37767                        self.config.dialect,
37768                        Some(crate::dialects::DialectType::Databricks)
37769                            | Some(crate::dialects::DialectType::Spark)
37770                    );
37771                if use_seed {
37772                    self.write_keyword("SEED");
37773                } else {
37774                    self.write_keyword("REPEATABLE");
37775                }
37776                self.write(" (");
37777                self.generate_expression(seed)?;
37778                self.write(")");
37779            }
37780            return Ok(());
37781        }
37782
37783        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
37784        self.write_keyword(self.config.tablesample_keywords);
37785        if let Some(method) = &e.method {
37786            self.write_space();
37787            self.write_keyword(method);
37788        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
37789            // Snowflake defaults to BERNOULLI when no method is specified
37790            self.write_space();
37791            self.write_keyword("BERNOULLI");
37792        }
37793        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
37794            self.write_space();
37795            self.write_keyword("BUCKET");
37796            self.write_space();
37797            self.generate_expression(numerator)?;
37798            self.write_space();
37799            self.write_keyword("OUT OF");
37800            self.write_space();
37801            self.generate_expression(denominator)?;
37802            if let Some(field) = &e.bucket_field {
37803                self.write_space();
37804                self.write_keyword("ON");
37805                self.write_space();
37806                self.generate_expression(field)?;
37807            }
37808        } else if !e.expressions.is_empty() {
37809            self.write(" (");
37810            for (i, expr) in e.expressions.iter().enumerate() {
37811                if i > 0 {
37812                    self.write(", ");
37813                }
37814                self.generate_expression(expr)?;
37815            }
37816            self.write(")");
37817        } else if let Some(percent) = &e.percent {
37818            self.write(" (");
37819            self.generate_expression(percent)?;
37820            self.write_space();
37821            self.write_keyword("PERCENT");
37822            self.write(")");
37823        }
37824        Ok(())
37825    }
37826
37827    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
37828        // [prefix]this[postfix]
37829        if let Some(prefix) = &e.prefix {
37830            self.generate_expression(prefix)?;
37831        }
37832        if let Some(this) = &e.this {
37833            self.generate_expression(this)?;
37834        }
37835        if let Some(postfix) = &e.postfix {
37836            self.generate_expression(postfix)?;
37837        }
37838        Ok(())
37839    }
37840
37841    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
37842        // TAG (expressions)
37843        self.write_keyword("TAG");
37844        self.write(" (");
37845        for (i, expr) in e.expressions.iter().enumerate() {
37846            if i > 0 {
37847                self.write(", ");
37848            }
37849            self.generate_expression(expr)?;
37850        }
37851        self.write(")");
37852        Ok(())
37853    }
37854
37855    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
37856        // TEMPORARY or TEMP or [this] TEMPORARY
37857        if let Some(this) = &e.this {
37858            self.generate_expression(this)?;
37859            self.write_space();
37860        }
37861        self.write_keyword("TEMPORARY");
37862        Ok(())
37863    }
37864
37865    /// Generate a Time function expression
37866    /// For most dialects: TIME('value')
37867    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
37868        // Standard: TIME(value)
37869        self.write_keyword("TIME");
37870        self.write("(");
37871        self.generate_expression(&e.this)?;
37872        self.write(")");
37873        Ok(())
37874    }
37875
37876    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
37877        // TIME_ADD(this, expression, unit)
37878        self.write_keyword("TIME_ADD");
37879        self.write("(");
37880        self.generate_expression(&e.this)?;
37881        self.write(", ");
37882        self.generate_expression(&e.expression)?;
37883        if let Some(unit) = &e.unit {
37884            self.write(", ");
37885            self.write_keyword(unit);
37886        }
37887        self.write(")");
37888        Ok(())
37889    }
37890
37891    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
37892        // TIME_DIFF(this, expression, unit)
37893        self.write_keyword("TIME_DIFF");
37894        self.write("(");
37895        self.generate_expression(&e.this)?;
37896        self.write(", ");
37897        self.generate_expression(&e.expression)?;
37898        if let Some(unit) = &e.unit {
37899            self.write(", ");
37900            self.write_keyword(unit);
37901        }
37902        self.write(")");
37903        Ok(())
37904    }
37905
37906    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
37907        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
37908        self.write_keyword("TIME_FROM_PARTS");
37909        self.write("(");
37910        let mut first = true;
37911        if let Some(hour) = &e.hour {
37912            self.generate_expression(hour)?;
37913            first = false;
37914        }
37915        if let Some(minute) = &e.min {
37916            if !first {
37917                self.write(", ");
37918            }
37919            self.generate_expression(minute)?;
37920            first = false;
37921        }
37922        if let Some(second) = &e.sec {
37923            if !first {
37924                self.write(", ");
37925            }
37926            self.generate_expression(second)?;
37927            first = false;
37928        }
37929        if let Some(ns) = &e.nano {
37930            if !first {
37931                self.write(", ");
37932            }
37933            self.generate_expression(ns)?;
37934        }
37935        self.write(")");
37936        Ok(())
37937    }
37938
37939    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
37940        // TIME_SLICE(this, expression, unit)
37941        self.write_keyword("TIME_SLICE");
37942        self.write("(");
37943        self.generate_expression(&e.this)?;
37944        self.write(", ");
37945        self.generate_expression(&e.expression)?;
37946        self.write(", ");
37947        self.write_keyword(&e.unit);
37948        self.write(")");
37949        Ok(())
37950    }
37951
37952    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
37953        // TIME_STR_TO_TIME(this)
37954        self.write_keyword("TIME_STR_TO_TIME");
37955        self.write("(");
37956        self.generate_expression(&e.this)?;
37957        self.write(")");
37958        Ok(())
37959    }
37960
37961    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
37962        // TIME_SUB(this, expression, unit)
37963        self.write_keyword("TIME_SUB");
37964        self.write("(");
37965        self.generate_expression(&e.this)?;
37966        self.write(", ");
37967        self.generate_expression(&e.expression)?;
37968        if let Some(unit) = &e.unit {
37969            self.write(", ");
37970            self.write_keyword(unit);
37971        }
37972        self.write(")");
37973        Ok(())
37974    }
37975
37976    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
37977        match self.config.dialect {
37978            Some(DialectType::Exasol) => {
37979                // Exasol uses TO_CHAR with Exasol-specific format
37980                self.write_keyword("TO_CHAR");
37981                self.write("(");
37982                self.generate_expression(&e.this)?;
37983                self.write(", '");
37984                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
37985                self.write("'");
37986                self.write(")");
37987            }
37988            Some(DialectType::PostgreSQL)
37989            | Some(DialectType::Redshift)
37990            | Some(DialectType::Materialize) => {
37991                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
37992                self.write_keyword("TO_CHAR");
37993                self.write("(");
37994                self.generate_expression(&e.this)?;
37995                self.write(", '");
37996                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
37997                self.write("'");
37998                self.write(")");
37999            }
38000            Some(DialectType::Oracle) => {
38001                // Oracle uses TO_CHAR with PG-like format
38002                self.write_keyword("TO_CHAR");
38003                self.write("(");
38004                self.generate_expression(&e.this)?;
38005                self.write(", '");
38006                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
38007                self.write("'");
38008                self.write(")");
38009            }
38010            Some(DialectType::Drill) => {
38011                // Drill: TO_CHAR with Java format
38012                self.write_keyword("TO_CHAR");
38013                self.write("(");
38014                self.generate_expression(&e.this)?;
38015                self.write(", '");
38016                self.write(&Self::strftime_to_java_format(&e.format));
38017                self.write("'");
38018                self.write(")");
38019            }
38020            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
38021                // TSQL: FORMAT(value, format) with .NET-style format
38022                self.write_keyword("FORMAT");
38023                self.write("(");
38024                self.generate_expression(&e.this)?;
38025                self.write(", '");
38026                self.write(&Self::strftime_to_tsql_format(&e.format));
38027                self.write("'");
38028                self.write(")");
38029            }
38030            Some(DialectType::DuckDB) => {
38031                // DuckDB: STRFTIME(value, format) - keeps C format
38032                self.write_keyword("STRFTIME");
38033                self.write("(");
38034                self.generate_expression(&e.this)?;
38035                self.write(", '");
38036                self.write(&e.format);
38037                self.write("'");
38038                self.write(")");
38039            }
38040            Some(DialectType::BigQuery) => {
38041                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
38042                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
38043                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
38044                self.write_keyword("FORMAT_DATE");
38045                self.write("('");
38046                self.write(&fmt);
38047                self.write("', ");
38048                self.generate_expression(&e.this)?;
38049                self.write(")");
38050            }
38051            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
38052                // Hive/Spark: DATE_FORMAT(value, java_format)
38053                self.write_keyword("DATE_FORMAT");
38054                self.write("(");
38055                self.generate_expression(&e.this)?;
38056                self.write(", '");
38057                self.write(&Self::strftime_to_java_format(&e.format));
38058                self.write("'");
38059                self.write(")");
38060            }
38061            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
38062                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
38063                self.write_keyword("DATE_FORMAT");
38064                self.write("(");
38065                self.generate_expression(&e.this)?;
38066                self.write(", '");
38067                self.write(&e.format);
38068                self.write("'");
38069                self.write(")");
38070            }
38071            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
38072                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
38073                self.write_keyword("DATE_FORMAT");
38074                self.write("(");
38075                self.generate_expression(&e.this)?;
38076                self.write(", '");
38077                self.write(&e.format);
38078                self.write("'");
38079                self.write(")");
38080            }
38081            _ => {
38082                // Default: TIME_TO_STR(this, format)
38083                self.write_keyword("TIME_TO_STR");
38084                self.write("(");
38085                self.generate_expression(&e.this)?;
38086                self.write(", '");
38087                self.write(&e.format);
38088                self.write("'");
38089                self.write(")");
38090            }
38091        }
38092        Ok(())
38093    }
38094
38095    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
38096        match self.config.dialect {
38097            Some(DialectType::DuckDB) => {
38098                // DuckDB: EPOCH(x)
38099                self.write_keyword("EPOCH");
38100                self.write("(");
38101                self.generate_expression(&e.this)?;
38102                self.write(")");
38103            }
38104            Some(DialectType::Hive)
38105            | Some(DialectType::Spark)
38106            | Some(DialectType::Databricks)
38107            | Some(DialectType::Doris)
38108            | Some(DialectType::StarRocks)
38109            | Some(DialectType::Drill) => {
38110                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
38111                self.write_keyword("UNIX_TIMESTAMP");
38112                self.write("(");
38113                self.generate_expression(&e.this)?;
38114                self.write(")");
38115            }
38116            Some(DialectType::Presto) | Some(DialectType::Trino) => {
38117                // Presto: TO_UNIXTIME(x)
38118                self.write_keyword("TO_UNIXTIME");
38119                self.write("(");
38120                self.generate_expression(&e.this)?;
38121                self.write(")");
38122            }
38123            _ => {
38124                // Default: TIME_TO_UNIX(x)
38125                self.write_keyword("TIME_TO_UNIX");
38126                self.write("(");
38127                self.generate_expression(&e.this)?;
38128                self.write(")");
38129            }
38130        }
38131        Ok(())
38132    }
38133
38134    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
38135        match self.config.dialect {
38136            Some(DialectType::Hive) => {
38137                // Hive: TO_DATE(x)
38138                self.write_keyword("TO_DATE");
38139                self.write("(");
38140                self.generate_expression(&e.this)?;
38141                self.write(")");
38142            }
38143            _ => {
38144                // Default: TIME_STR_TO_DATE(x)
38145                self.write_keyword("TIME_STR_TO_DATE");
38146                self.write("(");
38147                self.generate_expression(&e.this)?;
38148                self.write(")");
38149            }
38150        }
38151        Ok(())
38152    }
38153
38154    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
38155        // TIME_TRUNC(this, unit)
38156        self.write_keyword("TIME_TRUNC");
38157        self.write("(");
38158        self.generate_expression(&e.this)?;
38159        self.write(", ");
38160        self.write_keyword(&e.unit);
38161        self.write(")");
38162        Ok(())
38163    }
38164
38165    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
38166        // Just output the unit name
38167        if let Some(unit) = &e.unit {
38168            self.write_keyword(unit);
38169        }
38170        Ok(())
38171    }
38172
38173    /// Generate a Timestamp function expression
38174    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
38175    /// For other dialects: TIMESTAMP('value')
38176    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
38177        use crate::dialects::DialectType;
38178        use crate::expressions::Literal;
38179
38180        match self.config.dialect {
38181            // Exasol uses TO_TIMESTAMP for Timestamp expressions
38182            Some(DialectType::Exasol) => {
38183                self.write_keyword("TO_TIMESTAMP");
38184                self.write("(");
38185                // Extract the string value from the expression if it's a string literal
38186                if let Some(this) = &e.this {
38187                    match this.as_ref() {
38188                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
38189                            let Literal::String(s) = lit.as_ref() else {
38190                                unreachable!()
38191                            };
38192                            self.write("'");
38193                            self.write(s);
38194                            self.write("'");
38195                        }
38196                        _ => {
38197                            self.generate_expression(this)?;
38198                        }
38199                    }
38200                }
38201                self.write(")");
38202            }
38203            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
38204            _ => {
38205                self.write_keyword("TIMESTAMP");
38206                self.write("(");
38207                if let Some(this) = &e.this {
38208                    self.generate_expression(this)?;
38209                }
38210                if let Some(zone) = &e.zone {
38211                    self.write(", ");
38212                    self.generate_expression(zone)?;
38213                }
38214                self.write(")");
38215            }
38216        }
38217        Ok(())
38218    }
38219
38220    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
38221        // TIMESTAMP_ADD(this, expression, unit)
38222        self.write_keyword("TIMESTAMP_ADD");
38223        self.write("(");
38224        self.generate_expression(&e.this)?;
38225        self.write(", ");
38226        self.generate_expression(&e.expression)?;
38227        if let Some(unit) = &e.unit {
38228            self.write(", ");
38229            self.write_keyword(unit);
38230        }
38231        self.write(")");
38232        Ok(())
38233    }
38234
38235    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
38236        // TIMESTAMP_DIFF(this, expression, unit)
38237        self.write_keyword("TIMESTAMP_DIFF");
38238        self.write("(");
38239        self.generate_expression(&e.this)?;
38240        self.write(", ");
38241        self.generate_expression(&e.expression)?;
38242        if let Some(unit) = &e.unit {
38243            self.write(", ");
38244            self.write_keyword(unit);
38245        }
38246        self.write(")");
38247        Ok(())
38248    }
38249
38250    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
38251        // TIMESTAMP_FROM_PARTS(this, expression)
38252        self.write_keyword("TIMESTAMP_FROM_PARTS");
38253        self.write("(");
38254        if let Some(this) = &e.this {
38255            self.generate_expression(this)?;
38256        }
38257        if let Some(expression) = &e.expression {
38258            self.write(", ");
38259            self.generate_expression(expression)?;
38260        }
38261        if let Some(zone) = &e.zone {
38262            self.write(", ");
38263            self.generate_expression(zone)?;
38264        }
38265        if let Some(milli) = &e.milli {
38266            self.write(", ");
38267            self.generate_expression(milli)?;
38268        }
38269        self.write(")");
38270        Ok(())
38271    }
38272
38273    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
38274        // TIMESTAMP_SUB(this, INTERVAL expression unit)
38275        self.write_keyword("TIMESTAMP_SUB");
38276        self.write("(");
38277        self.generate_expression(&e.this)?;
38278        self.write(", ");
38279        self.write_keyword("INTERVAL");
38280        self.write_space();
38281        self.generate_expression(&e.expression)?;
38282        if let Some(unit) = &e.unit {
38283            self.write_space();
38284            self.write_keyword(unit);
38285        }
38286        self.write(")");
38287        Ok(())
38288    }
38289
38290    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
38291        // TIMESTAMP_TZ_FROM_PARTS(...)
38292        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
38293        self.write("(");
38294        if let Some(zone) = &e.zone {
38295            self.generate_expression(zone)?;
38296        }
38297        self.write(")");
38298        Ok(())
38299    }
38300
38301    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
38302        // TO_BINARY(this, [format])
38303        self.write_keyword("TO_BINARY");
38304        self.write("(");
38305        self.generate_expression(&e.this)?;
38306        if let Some(format) = &e.format {
38307            self.write(", '");
38308            self.write(format);
38309            self.write("'");
38310        }
38311        self.write(")");
38312        Ok(())
38313    }
38314
38315    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
38316        // TO_BOOLEAN(this)
38317        self.write_keyword("TO_BOOLEAN");
38318        self.write("(");
38319        self.generate_expression(&e.this)?;
38320        self.write(")");
38321        Ok(())
38322    }
38323
38324    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
38325        // TO_CHAR(this, [format], [nlsparam])
38326        self.write_keyword("TO_CHAR");
38327        self.write("(");
38328        self.generate_expression(&e.this)?;
38329        if let Some(format) = &e.format {
38330            self.write(", '");
38331            self.write(format);
38332            self.write("'");
38333        }
38334        if let Some(nlsparam) = &e.nlsparam {
38335            self.write(", ");
38336            self.generate_expression(nlsparam)?;
38337        }
38338        self.write(")");
38339        Ok(())
38340    }
38341
38342    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
38343        // TO_DECFLOAT(this, [format])
38344        self.write_keyword("TO_DECFLOAT");
38345        self.write("(");
38346        self.generate_expression(&e.this)?;
38347        if let Some(format) = &e.format {
38348            self.write(", '");
38349            self.write(format);
38350            self.write("'");
38351        }
38352        self.write(")");
38353        Ok(())
38354    }
38355
38356    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
38357        // TO_DOUBLE(this, [format])
38358        self.write_keyword("TO_DOUBLE");
38359        self.write("(");
38360        self.generate_expression(&e.this)?;
38361        if let Some(format) = &e.format {
38362            self.write(", '");
38363            self.write(format);
38364            self.write("'");
38365        }
38366        self.write(")");
38367        Ok(())
38368    }
38369
38370    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
38371        // TO_FILE(this, path)
38372        self.write_keyword("TO_FILE");
38373        self.write("(");
38374        self.generate_expression(&e.this)?;
38375        if let Some(path) = &e.path {
38376            self.write(", ");
38377            self.generate_expression(path)?;
38378        }
38379        self.write(")");
38380        Ok(())
38381    }
38382
38383    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
38384        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
38385        // If safe flag is set, output TRY_TO_NUMBER
38386        let is_safe = e.safe.is_some();
38387        if is_safe {
38388            self.write_keyword("TRY_TO_NUMBER");
38389        } else {
38390            self.write_keyword("TO_NUMBER");
38391        }
38392        self.write("(");
38393        self.generate_expression(&e.this)?;
38394        let precision_is_snowflake_default = e.precision.is_none()
38395            || matches!(
38396                e.precision.as_deref(),
38397                Some(Expression::Literal(lit))
38398                    if matches!(lit.as_ref(), Literal::Number(n) if n == "0")
38399            );
38400        let is_snowflake_default_precision =
38401            matches!(self.config.dialect, Some(DialectType::Snowflake))
38402                && e.nlsparam.is_none()
38403                && e.scale.is_none()
38404                && matches!(
38405                    e.format.as_deref(),
38406                    Some(Expression::Literal(lit))
38407                        if matches!(lit.as_ref(), Literal::Number(n) if n == "38")
38408                )
38409                && precision_is_snowflake_default;
38410
38411        if !is_snowflake_default_precision {
38412            if let Some(format) = &e.format {
38413                self.write(", ");
38414                self.generate_expression(format)?;
38415            }
38416            if let Some(nlsparam) = &e.nlsparam {
38417                self.write(", ");
38418                self.generate_expression(nlsparam)?;
38419            }
38420            if let Some(precision) = &e.precision {
38421                self.write(", ");
38422                self.generate_expression(precision)?;
38423            }
38424            if let Some(scale) = &e.scale {
38425                self.write(", ");
38426                self.generate_expression(scale)?;
38427            }
38428        }
38429        self.write(")");
38430        Ok(())
38431    }
38432
38433    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
38434        // TO_TABLE this
38435        self.write_keyword("TO_TABLE");
38436        self.write_space();
38437        self.generate_expression(&e.this)?;
38438        Ok(())
38439    }
38440
38441    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
38442        // Check mark to determine the format
38443        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
38444            Expression::Identifier(id) => id.name.clone(),
38445            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
38446                let Literal::String(s) = lit.as_ref() else {
38447                    unreachable!()
38448                };
38449                s.clone()
38450            }
38451            _ => String::new(),
38452        });
38453
38454        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
38455        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
38456        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
38457            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
38458        });
38459
38460        // For Presto/Trino: always use START TRANSACTION
38461        let use_start_transaction = matches!(
38462            self.config.dialect,
38463            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
38464        );
38465        // For most dialects: strip TRANSACTION keyword
38466        let strip_transaction = matches!(
38467            self.config.dialect,
38468            Some(DialectType::Snowflake)
38469                | Some(DialectType::PostgreSQL)
38470                | Some(DialectType::Redshift)
38471                | Some(DialectType::MySQL)
38472                | Some(DialectType::Hive)
38473                | Some(DialectType::Spark)
38474                | Some(DialectType::Databricks)
38475                | Some(DialectType::DuckDB)
38476                | Some(DialectType::Oracle)
38477                | Some(DialectType::Doris)
38478                | Some(DialectType::StarRocks)
38479                | Some(DialectType::Materialize)
38480                | Some(DialectType::ClickHouse)
38481        );
38482
38483        if is_start || use_start_transaction {
38484            // START TRANSACTION [modes]
38485            self.write_keyword("START TRANSACTION");
38486            if let Some(modes) = &e.modes {
38487                self.write_space();
38488                self.generate_expression(modes)?;
38489            }
38490        } else {
38491            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
38492            self.write_keyword("BEGIN");
38493
38494            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
38495            let is_kind = e.this.as_ref().map_or(false, |t| {
38496                if let Expression::Identifier(id) = t.as_ref() {
38497                    id.name.eq_ignore_ascii_case("DEFERRED")
38498                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
38499                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
38500                } else {
38501                    false
38502                }
38503            });
38504
38505            // Output kind before TRANSACTION keyword
38506            if is_kind {
38507                if let Some(this) = &e.this {
38508                    self.write_space();
38509                    if let Expression::Identifier(id) = this.as_ref() {
38510                        self.write_keyword(&id.name);
38511                    }
38512                }
38513            }
38514
38515            // Output TRANSACTION keyword if it was present and target supports it
38516            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
38517                self.write_space();
38518                self.write_keyword("TRANSACTION");
38519            }
38520
38521            // Output transaction name (not kind)
38522            if !is_kind {
38523                if let Some(this) = &e.this {
38524                    self.write_space();
38525                    self.generate_expression(this)?;
38526                }
38527            }
38528
38529            // Output WITH MARK 'description' for TSQL
38530            if has_with_mark {
38531                self.write_space();
38532                self.write_keyword("WITH MARK");
38533                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
38534                    if let Literal::String(desc) = lit.as_ref() {
38535                        if !desc.is_empty() {
38536                            self.write_space();
38537                            self.write(&format!("'{}'", desc));
38538                        }
38539                    }
38540                }
38541            }
38542
38543            // Output modes (isolation levels, etc.)
38544            if let Some(modes) = &e.modes {
38545                self.write_space();
38546                self.generate_expression(modes)?;
38547            }
38548        }
38549        Ok(())
38550    }
38551
38552    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
38553        // TRANSFORM(this, expression)
38554        self.write_keyword("TRANSFORM");
38555        self.write("(");
38556        self.generate_expression(&e.this)?;
38557        self.write(", ");
38558        self.generate_expression(&e.expression)?;
38559        self.write(")");
38560        Ok(())
38561    }
38562
38563    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
38564        // TRANSFORM(expressions)
38565        self.write_keyword("TRANSFORM");
38566        self.write("(");
38567        if self.config.pretty && !e.expressions.is_empty() {
38568            self.indent_level += 1;
38569            for (i, expr) in e.expressions.iter().enumerate() {
38570                if i > 0 {
38571                    self.write(",");
38572                }
38573                self.write_newline();
38574                self.write_indent();
38575                self.generate_expression(expr)?;
38576            }
38577            self.indent_level -= 1;
38578            self.write_newline();
38579            self.write(")");
38580        } else {
38581            for (i, expr) in e.expressions.iter().enumerate() {
38582                if i > 0 {
38583                    self.write(", ");
38584                }
38585                self.generate_expression(expr)?;
38586            }
38587            self.write(")");
38588        }
38589        Ok(())
38590    }
38591
38592    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
38593        use crate::dialects::DialectType;
38594        // TRANSIENT is Snowflake-specific; skip for other dialects
38595        if let Some(this) = &e.this {
38596            self.generate_expression(this)?;
38597            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
38598                self.write_space();
38599            }
38600        }
38601        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
38602            self.write_keyword("TRANSIENT");
38603        }
38604        Ok(())
38605    }
38606
38607    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
38608        // TRANSLATE(this, from_, to)
38609        self.write_keyword("TRANSLATE");
38610        self.write("(");
38611        self.generate_expression(&e.this)?;
38612        if let Some(from) = &e.from_ {
38613            self.write(", ");
38614            self.generate_expression(from)?;
38615        }
38616        if let Some(to) = &e.to {
38617            self.write(", ");
38618            self.generate_expression(to)?;
38619        }
38620        self.write(")");
38621        Ok(())
38622    }
38623
38624    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
38625        // TRANSLATE(this USING expression)
38626        self.write_keyword("TRANSLATE");
38627        self.write("(");
38628        self.generate_expression(&e.this)?;
38629        self.write_space();
38630        self.write_keyword("USING");
38631        self.write_space();
38632        self.generate_expression(&e.expression)?;
38633        if e.with_error.is_some() {
38634            self.write_space();
38635            self.write_keyword("WITH ERROR");
38636        }
38637        self.write(")");
38638        Ok(())
38639    }
38640
38641    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
38642        // TRUNCATE TABLE table1, table2, ...
38643        self.write_keyword("TRUNCATE TABLE");
38644        self.write_space();
38645        for (i, expr) in e.expressions.iter().enumerate() {
38646            if i > 0 {
38647                self.write(", ");
38648            }
38649            self.generate_expression(expr)?;
38650        }
38651        Ok(())
38652    }
38653
38654    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
38655        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
38656        self.write_keyword("TRY_BASE64_DECODE_BINARY");
38657        self.write("(");
38658        self.generate_expression(&e.this)?;
38659        if let Some(alphabet) = &e.alphabet {
38660            self.write(", ");
38661            self.generate_expression(alphabet)?;
38662        }
38663        self.write(")");
38664        Ok(())
38665    }
38666
38667    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
38668        // TRY_BASE64_DECODE_STRING(this, [alphabet])
38669        self.write_keyword("TRY_BASE64_DECODE_STRING");
38670        self.write("(");
38671        self.generate_expression(&e.this)?;
38672        if let Some(alphabet) = &e.alphabet {
38673            self.write(", ");
38674            self.generate_expression(alphabet)?;
38675        }
38676        self.write(")");
38677        Ok(())
38678    }
38679
38680    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
38681        // TRY_TO_DECFLOAT(this, [format])
38682        self.write_keyword("TRY_TO_DECFLOAT");
38683        self.write("(");
38684        self.generate_expression(&e.this)?;
38685        if let Some(format) = &e.format {
38686            self.write(", '");
38687            self.write(format);
38688            self.write("'");
38689        }
38690        self.write(")");
38691        Ok(())
38692    }
38693
38694    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
38695        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
38696        self.write_keyword("TS_OR_DS_ADD");
38697        self.write("(");
38698        self.generate_expression(&e.this)?;
38699        self.write(", ");
38700        self.generate_expression(&e.expression)?;
38701        if let Some(unit) = &e.unit {
38702            self.write(", ");
38703            self.write_keyword(unit);
38704        }
38705        if let Some(return_type) = &e.return_type {
38706            self.write(", ");
38707            self.generate_expression(return_type)?;
38708        }
38709        self.write(")");
38710        Ok(())
38711    }
38712
38713    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
38714        // TS_OR_DS_DIFF(this, expression, [unit])
38715        self.write_keyword("TS_OR_DS_DIFF");
38716        self.write("(");
38717        self.generate_expression(&e.this)?;
38718        self.write(", ");
38719        self.generate_expression(&e.expression)?;
38720        if let Some(unit) = &e.unit {
38721            self.write(", ");
38722            self.write_keyword(unit);
38723        }
38724        self.write(")");
38725        Ok(())
38726    }
38727
38728    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
38729        let default_time_format = "%Y-%m-%d %H:%M:%S";
38730        let default_date_format = "%Y-%m-%d";
38731        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
38732            f != default_time_format && f != default_date_format
38733        });
38734
38735        if has_non_default_format {
38736            // With non-default format: dialect-specific handling
38737            let fmt = e.format.as_ref().unwrap();
38738            match self.config.dialect {
38739                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
38740                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
38741                    // STR_TO_DATE is the MySQL-native form of StrToTime
38742                    let str_to_time = crate::expressions::StrToTime {
38743                        this: Box::new((*e.this).clone()),
38744                        format: fmt.clone(),
38745                        zone: None,
38746                        safe: None,
38747                        target_type: None,
38748                    };
38749                    self.generate_str_to_time(&str_to_time)?;
38750                }
38751                Some(DialectType::Hive)
38752                | Some(DialectType::Spark)
38753                | Some(DialectType::Databricks) => {
38754                    // Hive/Spark: TO_DATE(x, java_fmt)
38755                    self.write_keyword("TO_DATE");
38756                    self.write("(");
38757                    self.generate_expression(&e.this)?;
38758                    self.write(", '");
38759                    self.write(&Self::strftime_to_java_format(fmt));
38760                    self.write("')");
38761                }
38762                Some(DialectType::Snowflake) => {
38763                    // Snowflake: TO_DATE(x, snowflake_fmt)
38764                    self.write_keyword("TO_DATE");
38765                    self.write("(");
38766                    self.generate_expression(&e.this)?;
38767                    self.write(", '");
38768                    self.write(&Self::strftime_to_snowflake_format(fmt));
38769                    self.write("')");
38770                }
38771                Some(DialectType::Doris) => {
38772                    // Doris: TO_DATE(x) - ignores format
38773                    self.write_keyword("TO_DATE");
38774                    self.write("(");
38775                    self.generate_expression(&e.this)?;
38776                    self.write(")");
38777                }
38778                _ => {
38779                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
38780                    self.write_keyword("CAST");
38781                    self.write("(");
38782                    let str_to_time = crate::expressions::StrToTime {
38783                        this: Box::new((*e.this).clone()),
38784                        format: fmt.clone(),
38785                        zone: None,
38786                        safe: None,
38787                        target_type: None,
38788                    };
38789                    self.generate_str_to_time(&str_to_time)?;
38790                    self.write_keyword(" AS ");
38791                    self.write_keyword("DATE");
38792                    self.write(")");
38793                }
38794            }
38795        } else {
38796            // Without format (or default format): simple date conversion
38797            match self.config.dialect {
38798                Some(DialectType::MySQL)
38799                | Some(DialectType::SQLite)
38800                | Some(DialectType::StarRocks) => {
38801                    // MySQL/SQLite/StarRocks: DATE(x)
38802                    self.write_keyword("DATE");
38803                    self.write("(");
38804                    self.generate_expression(&e.this)?;
38805                    self.write(")");
38806                }
38807                Some(DialectType::Hive)
38808                | Some(DialectType::Spark)
38809                | Some(DialectType::Databricks)
38810                | Some(DialectType::Snowflake)
38811                | Some(DialectType::Doris) => {
38812                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
38813                    self.write_keyword("TO_DATE");
38814                    self.write("(");
38815                    self.generate_expression(&e.this)?;
38816                    self.write(")");
38817                }
38818                Some(DialectType::Presto)
38819                | Some(DialectType::Trino)
38820                | Some(DialectType::Athena) => {
38821                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
38822                    self.write_keyword("CAST");
38823                    self.write("(");
38824                    self.write_keyword("CAST");
38825                    self.write("(");
38826                    self.generate_expression(&e.this)?;
38827                    self.write_keyword(" AS ");
38828                    self.write_keyword("TIMESTAMP");
38829                    self.write(")");
38830                    self.write_keyword(" AS ");
38831                    self.write_keyword("DATE");
38832                    self.write(")");
38833                }
38834                Some(DialectType::ClickHouse) => {
38835                    // ClickHouse: CAST(x AS Nullable(DATE))
38836                    self.write_keyword("CAST");
38837                    self.write("(");
38838                    self.generate_expression(&e.this)?;
38839                    self.write_keyword(" AS ");
38840                    self.write("Nullable(DATE)");
38841                    self.write(")");
38842                }
38843                _ => {
38844                    // Default: CAST(x AS DATE)
38845                    self.write_keyword("CAST");
38846                    self.write("(");
38847                    self.generate_expression(&e.this)?;
38848                    self.write_keyword(" AS ");
38849                    self.write_keyword("DATE");
38850                    self.write(")");
38851                }
38852            }
38853        }
38854        Ok(())
38855    }
38856
38857    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
38858        // TS_OR_DS_TO_TIME(this, [format])
38859        self.write_keyword("TS_OR_DS_TO_TIME");
38860        self.write("(");
38861        self.generate_expression(&e.this)?;
38862        if let Some(format) = &e.format {
38863            self.write(", '");
38864            self.write(format);
38865            self.write("'");
38866        }
38867        self.write(")");
38868        Ok(())
38869    }
38870
38871    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
38872        // UNHEX(this, [expression])
38873        self.write_keyword("UNHEX");
38874        self.write("(");
38875        self.generate_expression(&e.this)?;
38876        if let Some(expression) = &e.expression {
38877            self.write(", ");
38878            self.generate_expression(expression)?;
38879        }
38880        self.write(")");
38881        Ok(())
38882    }
38883
38884    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
38885        // U&this [UESCAPE escape]
38886        self.write("U&");
38887        self.generate_expression(&e.this)?;
38888        if let Some(escape) = &e.escape {
38889            self.write_space();
38890            self.write_keyword("UESCAPE");
38891            self.write_space();
38892            self.generate_expression(escape)?;
38893        }
38894        Ok(())
38895    }
38896
38897    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
38898        // UNIFORM(this, expression, [gen], [seed])
38899        self.write_keyword("UNIFORM");
38900        self.write("(");
38901        self.generate_expression(&e.this)?;
38902        self.write(", ");
38903        self.generate_expression(&e.expression)?;
38904        if let Some(gen) = &e.gen {
38905            self.write(", ");
38906            self.generate_expression(gen)?;
38907        }
38908        if let Some(seed) = &e.seed {
38909            self.write(", ");
38910            self.generate_expression(seed)?;
38911        }
38912        self.write(")");
38913        Ok(())
38914    }
38915
38916    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
38917        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
38918        self.write_keyword("UNIQUE");
38919        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
38920        if e.nulls.is_some() {
38921            self.write(" NULLS NOT DISTINCT");
38922        }
38923        if let Some(this) = &e.this {
38924            self.write_space();
38925            self.generate_expression(this)?;
38926        }
38927        if let Some(index_type) = &e.index_type {
38928            self.write(" USING ");
38929            self.generate_expression(index_type)?;
38930        }
38931        if let Some(on_conflict) = &e.on_conflict {
38932            self.write_space();
38933            self.generate_expression(on_conflict)?;
38934        }
38935        for opt in &e.options {
38936            self.write_space();
38937            self.generate_expression(opt)?;
38938        }
38939        Ok(())
38940    }
38941
38942    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
38943        // UNIQUE KEY (expressions)
38944        self.write_keyword("UNIQUE KEY");
38945        self.write(" (");
38946        for (i, expr) in e.expressions.iter().enumerate() {
38947            if i > 0 {
38948                self.write(", ");
38949            }
38950            self.generate_expression(expr)?;
38951        }
38952        self.write(")");
38953        Ok(())
38954    }
38955
38956    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
38957        // ROLLUP (r1(col1, col2), r2(col1))
38958        self.write_keyword("ROLLUP");
38959        self.write(" (");
38960        for (i, index) in e.expressions.iter().enumerate() {
38961            if i > 0 {
38962                self.write(", ");
38963            }
38964            self.generate_identifier(&index.name)?;
38965            self.write("(");
38966            for (j, col) in index.expressions.iter().enumerate() {
38967                if j > 0 {
38968                    self.write(", ");
38969                }
38970                self.generate_identifier(col)?;
38971            }
38972            self.write(")");
38973        }
38974        self.write(")");
38975        Ok(())
38976    }
38977
38978    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
38979        match self.config.dialect {
38980            Some(DialectType::DuckDB) => {
38981                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
38982                self.write_keyword("STRFTIME");
38983                self.write("(");
38984                self.write_keyword("TO_TIMESTAMP");
38985                self.write("(");
38986                self.generate_expression(&e.this)?;
38987                self.write("), '");
38988                if let Some(format) = &e.format {
38989                    self.write(format);
38990                }
38991                self.write("')");
38992            }
38993            Some(DialectType::Hive) => {
38994                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
38995                self.write_keyword("FROM_UNIXTIME");
38996                self.write("(");
38997                self.generate_expression(&e.this)?;
38998                if let Some(format) = &e.format {
38999                    if format != "yyyy-MM-dd HH:mm:ss" {
39000                        self.write(", '");
39001                        self.write(format);
39002                        self.write("'");
39003                    }
39004                }
39005                self.write(")");
39006            }
39007            Some(DialectType::Presto) | Some(DialectType::Trino) => {
39008                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
39009                self.write_keyword("DATE_FORMAT");
39010                self.write("(");
39011                self.write_keyword("FROM_UNIXTIME");
39012                self.write("(");
39013                self.generate_expression(&e.this)?;
39014                self.write("), '");
39015                if let Some(format) = &e.format {
39016                    self.write(format);
39017                }
39018                self.write("')");
39019            }
39020            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
39021                // Spark: FROM_UNIXTIME(value, format)
39022                self.write_keyword("FROM_UNIXTIME");
39023                self.write("(");
39024                self.generate_expression(&e.this)?;
39025                if let Some(format) = &e.format {
39026                    self.write(", '");
39027                    self.write(format);
39028                    self.write("'");
39029                }
39030                self.write(")");
39031            }
39032            _ => {
39033                // Default: UNIX_TO_STR(this, [format])
39034                self.write_keyword("UNIX_TO_STR");
39035                self.write("(");
39036                self.generate_expression(&e.this)?;
39037                if let Some(format) = &e.format {
39038                    self.write(", '");
39039                    self.write(format);
39040                    self.write("'");
39041                }
39042                self.write(")");
39043            }
39044        }
39045        Ok(())
39046    }
39047
39048    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
39049        use crate::dialects::DialectType;
39050        let scale = e.scale.unwrap_or(0); // 0 = seconds
39051
39052        match self.config.dialect {
39053            Some(DialectType::Snowflake) => {
39054                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
39055                self.write_keyword("TO_TIMESTAMP");
39056                self.write("(");
39057                self.generate_expression(&e.this)?;
39058                if let Some(s) = e.scale {
39059                    if s > 0 {
39060                        self.write(", ");
39061                        self.write(&s.to_string());
39062                    }
39063                }
39064                self.write(")");
39065            }
39066            Some(DialectType::BigQuery) => {
39067                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
39068                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
39069                match scale {
39070                    0 => {
39071                        self.write_keyword("TIMESTAMP_SECONDS");
39072                        self.write("(");
39073                        self.generate_expression(&e.this)?;
39074                        self.write(")");
39075                    }
39076                    3 => {
39077                        self.write_keyword("TIMESTAMP_MILLIS");
39078                        self.write("(");
39079                        self.generate_expression(&e.this)?;
39080                        self.write(")");
39081                    }
39082                    6 => {
39083                        self.write_keyword("TIMESTAMP_MICROS");
39084                        self.write("(");
39085                        self.generate_expression(&e.this)?;
39086                        self.write(")");
39087                    }
39088                    _ => {
39089                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
39090                        self.write_keyword("TIMESTAMP_SECONDS");
39091                        self.write("(CAST(");
39092                        self.generate_expression(&e.this)?;
39093                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
39094                    }
39095                }
39096            }
39097            Some(DialectType::Spark) => {
39098                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
39099                // TIMESTAMP_MILLIS(value) for scale=3
39100                // TIMESTAMP_MICROS(value) for scale=6
39101                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
39102                match scale {
39103                    0 => {
39104                        self.write_keyword("CAST");
39105                        self.write("(");
39106                        self.write_keyword("FROM_UNIXTIME");
39107                        self.write("(");
39108                        self.generate_expression(&e.this)?;
39109                        self.write(") ");
39110                        self.write_keyword("AS TIMESTAMP");
39111                        self.write(")");
39112                    }
39113                    3 => {
39114                        self.write_keyword("TIMESTAMP_MILLIS");
39115                        self.write("(");
39116                        self.generate_expression(&e.this)?;
39117                        self.write(")");
39118                    }
39119                    6 => {
39120                        self.write_keyword("TIMESTAMP_MICROS");
39121                        self.write("(");
39122                        self.generate_expression(&e.this)?;
39123                        self.write(")");
39124                    }
39125                    _ => {
39126                        self.write_keyword("TIMESTAMP_SECONDS");
39127                        self.write("(");
39128                        self.generate_expression(&e.this)?;
39129                        self.write(&format!(" / POWER(10, {}))", scale));
39130                    }
39131                }
39132            }
39133            Some(DialectType::Databricks) => {
39134                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
39135                // TIMESTAMP_MILLIS(value) for scale=3
39136                // TIMESTAMP_MICROS(value) for scale=6
39137                match scale {
39138                    0 => {
39139                        self.write_keyword("CAST");
39140                        self.write("(");
39141                        self.write_keyword("FROM_UNIXTIME");
39142                        self.write("(");
39143                        self.generate_expression(&e.this)?;
39144                        self.write(") ");
39145                        self.write_keyword("AS TIMESTAMP");
39146                        self.write(")");
39147                    }
39148                    3 => {
39149                        self.write_keyword("TIMESTAMP_MILLIS");
39150                        self.write("(");
39151                        self.generate_expression(&e.this)?;
39152                        self.write(")");
39153                    }
39154                    6 => {
39155                        self.write_keyword("TIMESTAMP_MICROS");
39156                        self.write("(");
39157                        self.generate_expression(&e.this)?;
39158                        self.write(")");
39159                    }
39160                    _ => {
39161                        self.write_keyword("TIMESTAMP_SECONDS");
39162                        self.write("(");
39163                        self.generate_expression(&e.this)?;
39164                        self.write(&format!(" / POWER(10, {}))", scale));
39165                    }
39166                }
39167            }
39168            Some(DialectType::Hive) => {
39169                // Hive: FROM_UNIXTIME(value)
39170                if scale == 0 {
39171                    self.write_keyword("FROM_UNIXTIME");
39172                    self.write("(");
39173                    self.generate_expression(&e.this)?;
39174                    self.write(")");
39175                } else {
39176                    self.write_keyword("FROM_UNIXTIME");
39177                    self.write("(");
39178                    self.generate_expression(&e.this)?;
39179                    self.write(&format!(" / POWER(10, {})", scale));
39180                    self.write(")");
39181                }
39182            }
39183            Some(DialectType::Presto) | Some(DialectType::Trino) => {
39184                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
39185                // FROM_UNIXTIME(value) for scale=0
39186                if scale == 0 {
39187                    self.write_keyword("FROM_UNIXTIME");
39188                    self.write("(");
39189                    self.generate_expression(&e.this)?;
39190                    self.write(")");
39191                } else {
39192                    self.write_keyword("FROM_UNIXTIME");
39193                    self.write("(CAST(");
39194                    self.generate_expression(&e.this)?;
39195                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
39196                }
39197            }
39198            Some(DialectType::DuckDB) => {
39199                // DuckDB: TO_TIMESTAMP(value) for scale=0
39200                // EPOCH_MS(value) for scale=3
39201                // MAKE_TIMESTAMP(value) for scale=6
39202                match scale {
39203                    0 => {
39204                        self.write_keyword("TO_TIMESTAMP");
39205                        self.write("(");
39206                        self.generate_expression(&e.this)?;
39207                        self.write(")");
39208                    }
39209                    3 => {
39210                        self.write_keyword("EPOCH_MS");
39211                        self.write("(");
39212                        self.generate_expression(&e.this)?;
39213                        self.write(")");
39214                    }
39215                    6 => {
39216                        self.write_keyword("MAKE_TIMESTAMP");
39217                        self.write("(");
39218                        self.generate_expression(&e.this)?;
39219                        self.write(")");
39220                    }
39221                    _ => {
39222                        self.write_keyword("TO_TIMESTAMP");
39223                        self.write("(");
39224                        self.generate_expression(&e.this)?;
39225                        self.write(&format!(" / POWER(10, {}))", scale));
39226                        self.write_keyword(" AT TIME ZONE");
39227                        self.write(" 'UTC'");
39228                    }
39229                }
39230            }
39231            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
39232                // Doris/StarRocks: FROM_UNIXTIME(value)
39233                self.write_keyword("FROM_UNIXTIME");
39234                self.write("(");
39235                self.generate_expression(&e.this)?;
39236                self.write(")");
39237            }
39238            Some(DialectType::Oracle) => {
39239                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
39240                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
39241                self.generate_expression(&e.this)?;
39242                self.write(" / 86400)");
39243            }
39244            Some(DialectType::Redshift) => {
39245                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
39246                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
39247                self.write("(TIMESTAMP 'epoch' + ");
39248                if scale == 0 {
39249                    self.generate_expression(&e.this)?;
39250                } else {
39251                    self.write("(");
39252                    self.generate_expression(&e.this)?;
39253                    self.write(&format!(" / POWER(10, {}))", scale));
39254                }
39255                self.write(" * INTERVAL '1 SECOND')");
39256            }
39257            Some(DialectType::Exasol) => {
39258                // Exasol: FROM_POSIX_TIME(value)
39259                self.write_keyword("FROM_POSIX_TIME");
39260                self.write("(");
39261                self.generate_expression(&e.this)?;
39262                self.write(")");
39263            }
39264            _ => {
39265                // Default: TO_TIMESTAMP(value[, scale])
39266                self.write_keyword("TO_TIMESTAMP");
39267                self.write("(");
39268                self.generate_expression(&e.this)?;
39269                if let Some(s) = e.scale {
39270                    self.write(", ");
39271                    self.write(&s.to_string());
39272                }
39273                self.write(")");
39274            }
39275        }
39276        Ok(())
39277    }
39278
39279    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
39280        // NAME col VALUE col1, col2, ...
39281        if !matches!(&*e.this, Expression::Null(_)) {
39282            self.write_keyword("NAME");
39283            self.write_space();
39284            self.generate_expression(&e.this)?;
39285        }
39286        if !e.expressions.is_empty() {
39287            self.write_space();
39288            self.write_keyword("VALUE");
39289            self.write_space();
39290            for (i, expr) in e.expressions.iter().enumerate() {
39291                if i > 0 {
39292                    self.write(", ");
39293                }
39294                self.generate_expression(expr)?;
39295            }
39296        }
39297        Ok(())
39298    }
39299
39300    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
39301        // this(expressions) or (this)(expressions)
39302        if e.wrapped.is_some() {
39303            self.write("(");
39304        }
39305        self.generate_expression(&e.this)?;
39306        if e.wrapped.is_some() {
39307            self.write(")");
39308        }
39309        self.write("(");
39310        for (i, expr) in e.expressions.iter().enumerate() {
39311            if i > 0 {
39312                self.write(", ");
39313            }
39314            self.generate_expression(expr)?;
39315        }
39316        self.write(")");
39317        Ok(())
39318    }
39319
39320    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
39321        // USING TEMPLATE this
39322        self.write_keyword("USING TEMPLATE");
39323        self.write_space();
39324        self.generate_expression(&e.this)?;
39325        Ok(())
39326    }
39327
39328    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
39329        // UTC_TIME
39330        self.write_keyword("UTC_TIME");
39331        Ok(())
39332    }
39333
39334    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
39335        if matches!(
39336            self.config.dialect,
39337            Some(crate::dialects::DialectType::ClickHouse)
39338        ) {
39339            self.write_keyword("CURRENT_TIMESTAMP");
39340            self.write("('UTC')");
39341        } else {
39342            self.write_keyword("UTC_TIMESTAMP");
39343        }
39344        Ok(())
39345    }
39346
39347    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
39348        use crate::dialects::DialectType;
39349        // Choose UUID function name based on target dialect
39350        let func_name = match self.config.dialect {
39351            Some(DialectType::Snowflake) => "UUID_STRING",
39352            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
39353            Some(DialectType::BigQuery) => "GENERATE_UUID",
39354            _ => {
39355                if let Some(name) = &e.name {
39356                    name.as_str()
39357                } else {
39358                    "UUID"
39359                }
39360            }
39361        };
39362        self.write_keyword(func_name);
39363        self.write("(");
39364        if let Some(this) = &e.this {
39365            self.generate_expression(this)?;
39366        }
39367        self.write(")");
39368        Ok(())
39369    }
39370
39371    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
39372        // MAP(key1, value1, key2, value2, ...)
39373        self.write_keyword("MAP");
39374        self.write("(");
39375        let mut first = true;
39376        for (k, v) in e.keys.iter().zip(e.values.iter()) {
39377            if !first {
39378                self.write(", ");
39379            }
39380            self.generate_expression(k)?;
39381            self.write(", ");
39382            self.generate_expression(v)?;
39383            first = false;
39384        }
39385        self.write(")");
39386        Ok(())
39387    }
39388
39389    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
39390        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
39391        self.write_keyword("VECTOR_SEARCH");
39392        self.write("(");
39393        self.generate_expression(&e.this)?;
39394        if let Some(col) = &e.column_to_search {
39395            self.write(", ");
39396            self.generate_expression(col)?;
39397        }
39398        if let Some(query_table) = &e.query_table {
39399            self.write(", ");
39400            self.generate_expression(query_table)?;
39401        }
39402        if let Some(query_col) = &e.query_column_to_search {
39403            self.write(", ");
39404            self.generate_expression(query_col)?;
39405        }
39406        if let Some(top_k) = &e.top_k {
39407            self.write(", ");
39408            self.generate_expression(top_k)?;
39409        }
39410        if let Some(dist_type) = &e.distance_type {
39411            self.write(", ");
39412            self.generate_expression(dist_type)?;
39413        }
39414        self.write(")");
39415        Ok(())
39416    }
39417
39418    fn generate_version(&mut self, e: &Version) -> Result<()> {
39419        // Python: f"FOR {expression.name} {kind} {expr}"
39420        // e.this = Identifier("TIMESTAMP" or "VERSION")
39421        // e.kind = "AS OF" (or "BETWEEN", etc.)
39422        // e.expression = the value expression
39423        // Hive does NOT use the FOR prefix for time travel
39424        use crate::dialects::DialectType;
39425        let skip_for = matches!(
39426            self.config.dialect,
39427            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
39428        );
39429        if !skip_for {
39430            self.write_keyword("FOR");
39431            self.write_space();
39432        }
39433        // Extract the name from this (which is an Identifier expression)
39434        match e.this.as_ref() {
39435            Expression::Identifier(ident) => {
39436                self.write_keyword(&ident.name);
39437            }
39438            _ => {
39439                self.generate_expression(&e.this)?;
39440            }
39441        }
39442        self.write_space();
39443        self.write_keyword(&e.kind);
39444        if let Some(expression) = &e.expression {
39445            self.write_space();
39446            self.generate_expression(expression)?;
39447        }
39448        Ok(())
39449    }
39450
39451    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
39452        // Python: return self.sql(expression, "this")
39453        self.generate_expression(&e.this)?;
39454        Ok(())
39455    }
39456
39457    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
39458        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
39459        if e.this.is_some() {
39460            self.write_keyword("NOT VOLATILE");
39461        } else {
39462            self.write_keyword("VOLATILE");
39463        }
39464        Ok(())
39465    }
39466
39467    fn generate_watermark_column_constraint(
39468        &mut self,
39469        e: &WatermarkColumnConstraint,
39470    ) -> Result<()> {
39471        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
39472        self.write_keyword("WATERMARK FOR");
39473        self.write_space();
39474        self.generate_expression(&e.this)?;
39475        self.write_space();
39476        self.write_keyword("AS");
39477        self.write_space();
39478        self.generate_expression(&e.expression)?;
39479        Ok(())
39480    }
39481
39482    fn generate_week(&mut self, e: &Week) -> Result<()> {
39483        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
39484        self.write_keyword("WEEK");
39485        self.write("(");
39486        self.generate_expression(&e.this)?;
39487        if let Some(mode) = &e.mode {
39488            self.write(", ");
39489            self.generate_expression(mode)?;
39490        }
39491        self.write(")");
39492        Ok(())
39493    }
39494
39495    fn generate_when(&mut self, e: &When) -> Result<()> {
39496        // Python: WHEN {matched}{source}{condition} THEN {then}
39497        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
39498        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
39499        self.write_keyword("WHEN");
39500        self.write_space();
39501
39502        // Check if matched
39503        if let Some(matched) = &e.matched {
39504            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
39505            match matched.as_ref() {
39506                Expression::Boolean(b) if b.value => {
39507                    self.write_keyword("MATCHED");
39508                }
39509                _ => {
39510                    self.write_keyword("NOT MATCHED");
39511                }
39512            }
39513        } else {
39514            self.write_keyword("NOT MATCHED");
39515        }
39516
39517        // BY SOURCE / BY TARGET
39518        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
39519        // BY TARGET is the default and typically omitted in output
39520        // Only emit if the dialect supports BY SOURCE syntax
39521        if self.config.matched_by_source {
39522            if let Some(source) = &e.source {
39523                if let Expression::Boolean(b) = source.as_ref() {
39524                    if b.value {
39525                        // BY SOURCE
39526                        self.write_space();
39527                        self.write_keyword("BY SOURCE");
39528                    }
39529                    // BY TARGET (b.value == false) is omitted as it's the default
39530                } else {
39531                    // For non-boolean source, output as BY SOURCE (legacy behavior)
39532                    self.write_space();
39533                    self.write_keyword("BY SOURCE");
39534                }
39535            }
39536        }
39537
39538        // Condition
39539        if let Some(condition) = &e.condition {
39540            self.write_space();
39541            self.write_keyword("AND");
39542            self.write_space();
39543            self.generate_expression(condition)?;
39544        }
39545
39546        self.write_space();
39547        self.write_keyword("THEN");
39548        self.write_space();
39549
39550        // Generate the then expression (could be INSERT, UPDATE, DELETE)
39551        // MERGE actions are stored as Tuples with the action keyword as first element
39552        self.generate_merge_action(&e.then)?;
39553
39554        Ok(())
39555    }
39556
39557    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
39558        match action {
39559            Expression::Tuple(tuple) => {
39560                let elements = &tuple.expressions;
39561                if elements.is_empty() {
39562                    return self.generate_expression(action);
39563                }
39564                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
39565                match &elements[0] {
39566                    Expression::Var(v) if v.this == "INSERT" => {
39567                        self.write_keyword("INSERT");
39568                        // Spark: INSERT * (insert all columns)
39569                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
39570                            self.write(" *");
39571                            if let Some(Expression::Where(w)) = elements.get(2) {
39572                                self.write_space();
39573                                self.generate_where(w)?;
39574                            }
39575                        } else {
39576                            let mut values_idx = 1;
39577                            // Check if second element is column list (Tuple)
39578                            if elements.len() > 1 {
39579                                if let Expression::Tuple(cols) = &elements[1] {
39580                                    // Could be columns or values - if there's a third element, second is columns
39581                                    if elements.len() > 2 {
39582                                        // Second is columns, third is values
39583                                        self.write(" (");
39584                                        for (i, col) in cols.expressions.iter().enumerate() {
39585                                            if i > 0 {
39586                                                self.write(", ");
39587                                            }
39588                                            // Strip MERGE target qualifiers from INSERT column list
39589                                            if !self.merge_strip_qualifiers.is_empty() {
39590                                                let stripped = self.strip_merge_qualifier(col);
39591                                                self.generate_expression(&stripped)?;
39592                                            } else {
39593                                                self.generate_expression(col)?;
39594                                            }
39595                                        }
39596                                        self.write(")");
39597                                        values_idx = 2;
39598                                    } else {
39599                                        // Only two elements: INSERT + values (no explicit columns)
39600                                        values_idx = 1;
39601                                    }
39602                                }
39603                            }
39604                            let mut next_idx = values_idx;
39605                            // Generate VALUES clause
39606                            if values_idx < elements.len()
39607                                && !matches!(&elements[values_idx], Expression::Where(_))
39608                            {
39609                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
39610                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
39611                                if !is_row {
39612                                    self.write_space();
39613                                    self.write_keyword("VALUES");
39614                                }
39615                                self.write(" ");
39616                                if let Expression::Tuple(vals) = &elements[values_idx] {
39617                                    self.write("(");
39618                                    for (i, val) in vals.expressions.iter().enumerate() {
39619                                        if i > 0 {
39620                                            self.write(", ");
39621                                        }
39622                                        self.generate_expression(val)?;
39623                                    }
39624                                    self.write(")");
39625                                } else {
39626                                    self.generate_expression(&elements[values_idx])?;
39627                                }
39628                                next_idx += 1;
39629                            }
39630                            if let Some(Expression::Where(w)) = elements.get(next_idx) {
39631                                self.write_space();
39632                                self.generate_where(w)?;
39633                            }
39634                        } // close else for INSERT * check
39635                    }
39636                    Expression::Var(v) if v.this == "UPDATE" => {
39637                        self.write_keyword("UPDATE");
39638                        // Spark: UPDATE * (update all columns)
39639                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
39640                            self.write(" *");
39641                            if let Some(Expression::Where(w)) = elements.get(2) {
39642                                self.write_space();
39643                                self.generate_where(w)?;
39644                            }
39645                        } else if elements.len() > 1 {
39646                            self.write_space();
39647                            self.write_keyword("SET");
39648                            // In pretty mode, put assignments on next line with extra indent
39649                            if self.config.pretty {
39650                                self.write_newline();
39651                                self.indent_level += 1;
39652                                self.write_indent();
39653                            } else {
39654                                self.write_space();
39655                            }
39656                            if let Expression::Tuple(assignments) = &elements[1] {
39657                                for (i, assignment) in assignments.expressions.iter().enumerate() {
39658                                    if i > 0 {
39659                                        if self.config.pretty {
39660                                            self.write(",");
39661                                            self.write_newline();
39662                                            self.write_indent();
39663                                        } else {
39664                                            self.write(", ");
39665                                        }
39666                                    }
39667                                    // Strip MERGE target qualifiers from left side of UPDATE SET
39668                                    if !self.merge_strip_qualifiers.is_empty() {
39669                                        self.generate_merge_set_assignment(assignment)?;
39670                                    } else {
39671                                        self.generate_expression(assignment)?;
39672                                    }
39673                                }
39674                            } else {
39675                                self.generate_expression(&elements[1])?;
39676                            }
39677                            if self.config.pretty {
39678                                self.indent_level -= 1;
39679                            }
39680                            if let Some(Expression::Where(w)) = elements.get(2) {
39681                                self.write_space();
39682                                self.generate_where(w)?;
39683                            }
39684                        }
39685                    }
39686                    Expression::Var(v) if v.this == "DELETE" => {
39687                        self.write_keyword("DELETE");
39688                        if let Some(Expression::Where(w)) = elements.get(1) {
39689                            self.write_space();
39690                            self.generate_where(w)?;
39691                        }
39692                    }
39693                    _ => {
39694                        // Fallback: generic tuple generation
39695                        self.generate_expression(action)?;
39696                    }
39697                }
39698            }
39699            Expression::Var(v)
39700                if v.this == "INSERT"
39701                    || v.this == "UPDATE"
39702                    || v.this == "DELETE"
39703                    || v.this == "DO NOTHING" =>
39704            {
39705                self.write_keyword(&v.this);
39706            }
39707            _ => {
39708                self.generate_expression(action)?;
39709            }
39710        }
39711        Ok(())
39712    }
39713
39714    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
39715    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
39716        match assignment {
39717            Expression::Eq(eq) => {
39718                // Strip qualifier from the left side if it matches a MERGE target name
39719                let stripped_left = self.strip_merge_qualifier(&eq.left);
39720                self.generate_expression(&stripped_left)?;
39721                self.write(" = ");
39722                self.generate_expression(&eq.right)?;
39723                Ok(())
39724            }
39725            other => self.generate_expression(other),
39726        }
39727    }
39728
39729    /// Strip table qualifier from a column reference if it matches a MERGE target name
39730    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
39731        match expr {
39732            Expression::Column(col) => {
39733                if let Some(ref table_ident) = col.table {
39734                    if self
39735                        .merge_strip_qualifiers
39736                        .iter()
39737                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
39738                    {
39739                        // Strip the table qualifier
39740                        let mut col = col.clone();
39741                        col.table = None;
39742                        return Expression::Column(col);
39743                    }
39744                }
39745                expr.clone()
39746            }
39747            Expression::Dot(dot) => {
39748                // table.column -> column (strip qualifier)
39749                if let Expression::Identifier(id) = &dot.this {
39750                    if self
39751                        .merge_strip_qualifiers
39752                        .iter()
39753                        .any(|n| n.eq_ignore_ascii_case(&id.name))
39754                    {
39755                        return Expression::Identifier(dot.field.clone());
39756                    }
39757                }
39758                expr.clone()
39759            }
39760            _ => expr.clone(),
39761        }
39762    }
39763
39764    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
39765        // Python: return self.expressions(expression, sep=" ", indent=False)
39766        for (i, expr) in e.expressions.iter().enumerate() {
39767            if i > 0 {
39768                // In pretty mode, each WHEN clause on its own line
39769                if self.config.pretty {
39770                    self.write_newline();
39771                    self.write_indent();
39772                } else {
39773                    self.write_space();
39774                }
39775            }
39776            self.generate_expression(expr)?;
39777        }
39778        Ok(())
39779    }
39780
39781    fn generate_where(&mut self, e: &Where) -> Result<()> {
39782        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
39783        self.write_keyword("WHERE");
39784        self.write_space();
39785        self.generate_expression(&e.this)?;
39786        Ok(())
39787    }
39788
39789    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
39790        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
39791        self.write_keyword("WIDTH_BUCKET");
39792        self.write("(");
39793        self.generate_expression(&e.this)?;
39794        if let Some(min_value) = &e.min_value {
39795            self.write(", ");
39796            self.generate_expression(min_value)?;
39797        }
39798        if let Some(max_value) = &e.max_value {
39799            self.write(", ");
39800            self.generate_expression(max_value)?;
39801        }
39802        if let Some(num_buckets) = &e.num_buckets {
39803            self.write(", ");
39804            self.generate_expression(num_buckets)?;
39805        }
39806        self.write(")");
39807        Ok(())
39808    }
39809
39810    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
39811        // Window specification: PARTITION BY ... ORDER BY ... frame
39812        self.generate_window_spec(e)
39813    }
39814
39815    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
39816        // Window specification: PARTITION BY ... ORDER BY ... frame
39817        let mut has_content = false;
39818
39819        // PARTITION BY
39820        if !e.partition_by.is_empty() {
39821            self.write_keyword("PARTITION BY");
39822            self.write_space();
39823            for (i, expr) in e.partition_by.iter().enumerate() {
39824                if i > 0 {
39825                    self.write(", ");
39826                }
39827                self.generate_expression(expr)?;
39828            }
39829            has_content = true;
39830        }
39831
39832        // ORDER BY
39833        if !e.order_by.is_empty() {
39834            if has_content {
39835                self.write_space();
39836            }
39837            self.write_keyword("ORDER BY");
39838            self.write_space();
39839            for (i, ordered) in e.order_by.iter().enumerate() {
39840                if i > 0 {
39841                    self.write(", ");
39842                }
39843                self.generate_expression(&ordered.this)?;
39844                if ordered.desc {
39845                    self.write_space();
39846                    self.write_keyword("DESC");
39847                } else if ordered.explicit_asc {
39848                    self.write_space();
39849                    self.write_keyword("ASC");
39850                }
39851                if let Some(nulls_first) = ordered.nulls_first {
39852                    self.write_space();
39853                    self.write_keyword("NULLS");
39854                    self.write_space();
39855                    if nulls_first {
39856                        self.write_keyword("FIRST");
39857                    } else {
39858                        self.write_keyword("LAST");
39859                    }
39860                }
39861            }
39862            has_content = true;
39863        }
39864
39865        // Frame specification
39866        if let Some(frame) = &e.frame {
39867            if has_content {
39868                self.write_space();
39869            }
39870            self.generate_window_frame(frame)?;
39871        }
39872
39873        Ok(())
39874    }
39875
39876    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
39877        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
39878        self.write_keyword("WITH");
39879        self.write_space();
39880        if e.no.is_some() {
39881            self.write_keyword("NO");
39882            self.write_space();
39883        }
39884        self.write_keyword("DATA");
39885
39886        // statistics
39887        if let Some(statistics) = &e.statistics {
39888            self.write_space();
39889            self.write_keyword("AND");
39890            self.write_space();
39891            // Check if statistics is true or false
39892            match statistics.as_ref() {
39893                Expression::Boolean(b) if !b.value => {
39894                    self.write_keyword("NO");
39895                    self.write_space();
39896                }
39897                _ => {}
39898            }
39899            self.write_keyword("STATISTICS");
39900        }
39901        Ok(())
39902    }
39903
39904    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
39905        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
39906        self.write_keyword("WITH FILL");
39907
39908        if let Some(from_) = &e.from_ {
39909            self.write_space();
39910            self.write_keyword("FROM");
39911            self.write_space();
39912            self.generate_expression(from_)?;
39913        }
39914
39915        if let Some(to) = &e.to {
39916            self.write_space();
39917            self.write_keyword("TO");
39918            self.write_space();
39919            self.generate_expression(to)?;
39920        }
39921
39922        if let Some(step) = &e.step {
39923            self.write_space();
39924            self.write_keyword("STEP");
39925            self.write_space();
39926            self.generate_expression(step)?;
39927        }
39928
39929        if let Some(staleness) = &e.staleness {
39930            self.write_space();
39931            self.write_keyword("STALENESS");
39932            self.write_space();
39933            self.generate_expression(staleness)?;
39934        }
39935
39936        if let Some(interpolate) = &e.interpolate {
39937            self.write_space();
39938            self.write_keyword("INTERPOLATE");
39939            self.write(" (");
39940            // INTERPOLATE items use reversed alias format: name AS expression
39941            self.generate_interpolate_item(interpolate)?;
39942            self.write(")");
39943        }
39944
39945        Ok(())
39946    }
39947
39948    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
39949    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
39950        match expr {
39951            Expression::Alias(alias) => {
39952                // Output as: alias_name AS expression
39953                self.generate_identifier(&alias.alias)?;
39954                self.write_space();
39955                self.write_keyword("AS");
39956                self.write_space();
39957                self.generate_expression(&alias.this)?;
39958            }
39959            Expression::Tuple(tuple) => {
39960                for (i, item) in tuple.expressions.iter().enumerate() {
39961                    if i > 0 {
39962                        self.write(", ");
39963                    }
39964                    self.generate_interpolate_item(item)?;
39965                }
39966            }
39967            other => {
39968                self.generate_expression(other)?;
39969            }
39970        }
39971        Ok(())
39972    }
39973
39974    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
39975        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
39976        self.write_keyword("WITH JOURNAL TABLE");
39977        self.write("=");
39978        self.generate_expression(&e.this)?;
39979        Ok(())
39980    }
39981
39982    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
39983        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
39984        self.generate_expression(&e.this)?;
39985        self.write_space();
39986        self.write_keyword("WITH");
39987        self.write_space();
39988        self.write_keyword(&e.op);
39989        Ok(())
39990    }
39991
39992    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
39993        // Python: return f"WITH {self.expressions(expression, flat=True)}"
39994        self.write_keyword("WITH");
39995        self.write_space();
39996        for (i, expr) in e.expressions.iter().enumerate() {
39997            if i > 0 {
39998                self.write(", ");
39999            }
40000            self.generate_expression(expr)?;
40001        }
40002        Ok(())
40003    }
40004
40005    fn generate_with_schema_binding_property(
40006        &mut self,
40007        e: &WithSchemaBindingProperty,
40008    ) -> Result<()> {
40009        // Python: return f"WITH {self.sql(expression, 'this')}"
40010        self.write_keyword("WITH");
40011        self.write_space();
40012        self.generate_expression(&e.this)?;
40013        Ok(())
40014    }
40015
40016    fn generate_with_system_versioning_property(
40017        &mut self,
40018        e: &WithSystemVersioningProperty,
40019    ) -> Result<()> {
40020        // Python: complex logic for SYSTEM_VERSIONING with options
40021        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
40022        // or SYSTEM_VERSIONING=ON/OFF
40023        // with WITH(...) wrapper if with_ is set
40024
40025        let mut parts = Vec::new();
40026
40027        if let Some(this) = &e.this {
40028            // HISTORY_TABLE=...
40029            let mut s = String::from("HISTORY_TABLE=");
40030            let mut gen = Generator::new();
40031            gen.generate_expression(this)?;
40032            s.push_str(&gen.output);
40033            parts.push(s);
40034        }
40035
40036        if let Some(data_consistency) = &e.data_consistency {
40037            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
40038            let mut gen = Generator::new();
40039            gen.generate_expression(data_consistency)?;
40040            s.push_str(&gen.output);
40041            parts.push(s);
40042        }
40043
40044        if let Some(retention_period) = &e.retention_period {
40045            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
40046            let mut gen = Generator::new();
40047            gen.generate_expression(retention_period)?;
40048            s.push_str(&gen.output);
40049            parts.push(s);
40050        }
40051
40052        self.write_keyword("SYSTEM_VERSIONING");
40053        self.write("=");
40054
40055        if !parts.is_empty() {
40056            self.write_keyword("ON");
40057            self.write("(");
40058            self.write(&parts.join(", "));
40059            self.write(")");
40060        } else if e.on.is_some() {
40061            self.write_keyword("ON");
40062        } else {
40063            self.write_keyword("OFF");
40064        }
40065
40066        // Wrap in WITH(...) if with_ is set
40067        if e.with_.is_some() {
40068            let inner = self.output.clone();
40069            self.output.clear();
40070            self.write("WITH(");
40071            self.write(&inner);
40072            self.write(")");
40073        }
40074
40075        Ok(())
40076    }
40077
40078    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
40079        // Python: f"WITH ({self.expressions(expression, flat=True)})"
40080        self.write_keyword("WITH");
40081        self.write(" (");
40082        for (i, expr) in e.expressions.iter().enumerate() {
40083            if i > 0 {
40084                self.write(", ");
40085            }
40086            self.generate_expression(expr)?;
40087        }
40088        self.write(")");
40089        Ok(())
40090    }
40091
40092    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
40093        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
40094        // return self.func("XMLELEMENT", name, *expression.expressions)
40095        self.write_keyword("XMLELEMENT");
40096        self.write("(");
40097
40098        if e.evalname.is_some() {
40099            self.write_keyword("EVALNAME");
40100        } else {
40101            self.write_keyword("NAME");
40102        }
40103        self.write_space();
40104        self.generate_expression(&e.this)?;
40105
40106        for expr in &e.expressions {
40107            self.write(", ");
40108            self.generate_expression(expr)?;
40109        }
40110        self.write(")");
40111        Ok(())
40112    }
40113
40114    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
40115        // XMLGET(this, expression [, instance])
40116        self.write_keyword("XMLGET");
40117        self.write("(");
40118        self.generate_expression(&e.this)?;
40119        self.write(", ");
40120        self.generate_expression(&e.expression)?;
40121        if let Some(instance) = &e.instance {
40122            self.write(", ");
40123            self.generate_expression(instance)?;
40124        }
40125        self.write(")");
40126        Ok(())
40127    }
40128
40129    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
40130        // Python: this + optional (expr)
40131        self.generate_expression(&e.this)?;
40132        if let Some(expression) = &e.expression {
40133            self.write("(");
40134            self.generate_expression(expression)?;
40135            self.write(")");
40136        }
40137        Ok(())
40138    }
40139
40140    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
40141        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
40142        self.write_keyword("XMLTABLE");
40143        self.write("(");
40144
40145        if self.config.pretty {
40146            self.indent_level += 1;
40147            self.write_newline();
40148            self.write_indent();
40149            self.generate_expression(&e.this)?;
40150
40151            if let Some(passing) = &e.passing {
40152                self.write_newline();
40153                self.write_indent();
40154                self.write_keyword("PASSING");
40155                if let Expression::Tuple(tuple) = passing.as_ref() {
40156                    for expr in &tuple.expressions {
40157                        self.write_newline();
40158                        self.indent_level += 1;
40159                        self.write_indent();
40160                        self.generate_expression(expr)?;
40161                        self.indent_level -= 1;
40162                    }
40163                } else {
40164                    self.write_newline();
40165                    self.indent_level += 1;
40166                    self.write_indent();
40167                    self.generate_expression(passing)?;
40168                    self.indent_level -= 1;
40169                }
40170            }
40171
40172            if e.by_ref.is_some() {
40173                self.write_newline();
40174                self.write_indent();
40175                self.write_keyword("RETURNING SEQUENCE BY REF");
40176            }
40177
40178            if !e.columns.is_empty() {
40179                self.write_newline();
40180                self.write_indent();
40181                self.write_keyword("COLUMNS");
40182                for (i, col) in e.columns.iter().enumerate() {
40183                    self.write_newline();
40184                    self.indent_level += 1;
40185                    self.write_indent();
40186                    self.generate_expression(col)?;
40187                    self.indent_level -= 1;
40188                    if i < e.columns.len() - 1 {
40189                        self.write(",");
40190                    }
40191                }
40192            }
40193
40194            self.indent_level -= 1;
40195            self.write_newline();
40196            self.write_indent();
40197            self.write(")");
40198            return Ok(());
40199        }
40200
40201        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
40202        if let Some(namespaces) = &e.namespaces {
40203            self.write_keyword("XMLNAMESPACES");
40204            self.write("(");
40205            // Unwrap Tuple if present to avoid extra parentheses
40206            if let Expression::Tuple(tuple) = namespaces.as_ref() {
40207                for (i, expr) in tuple.expressions.iter().enumerate() {
40208                    if i > 0 {
40209                        self.write(", ");
40210                    }
40211                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
40212                    // See xmlnamespace_sql in generator.py
40213                    if !matches!(expr, Expression::Alias(_)) {
40214                        self.write_keyword("DEFAULT");
40215                        self.write_space();
40216                    }
40217                    self.generate_expression(expr)?;
40218                }
40219            } else {
40220                // Single namespace - check if DEFAULT
40221                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
40222                    self.write_keyword("DEFAULT");
40223                    self.write_space();
40224                }
40225                self.generate_expression(namespaces)?;
40226            }
40227            self.write("), ");
40228        }
40229
40230        // XPath expression
40231        self.generate_expression(&e.this)?;
40232
40233        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
40234        if let Some(passing) = &e.passing {
40235            self.write_space();
40236            self.write_keyword("PASSING");
40237            self.write_space();
40238            // Unwrap Tuple if present to avoid extra parentheses
40239            if let Expression::Tuple(tuple) = passing.as_ref() {
40240                for (i, expr) in tuple.expressions.iter().enumerate() {
40241                    if i > 0 {
40242                        self.write(", ");
40243                    }
40244                    self.generate_expression(expr)?;
40245                }
40246            } else {
40247                self.generate_expression(passing)?;
40248            }
40249        }
40250
40251        // RETURNING SEQUENCE BY REF
40252        if e.by_ref.is_some() {
40253            self.write_space();
40254            self.write_keyword("RETURNING SEQUENCE BY REF");
40255        }
40256
40257        // COLUMNS clause
40258        if !e.columns.is_empty() {
40259            self.write_space();
40260            self.write_keyword("COLUMNS");
40261            self.write_space();
40262            for (i, col) in e.columns.iter().enumerate() {
40263                if i > 0 {
40264                    self.write(", ");
40265                }
40266                self.generate_expression(col)?;
40267            }
40268        }
40269
40270        self.write(")");
40271        Ok(())
40272    }
40273
40274    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
40275        // Python: return self.connector_sql(expression, "XOR", stack)
40276        // Handles: this XOR expression or expressions joined by XOR
40277        if let Some(this) = &e.this {
40278            self.generate_expression(this)?;
40279            if let Some(expression) = &e.expression {
40280                self.write_space();
40281                self.write_keyword("XOR");
40282                self.write_space();
40283                self.generate_expression(expression)?;
40284            }
40285        }
40286
40287        // Handle multiple expressions
40288        for (i, expr) in e.expressions.iter().enumerate() {
40289            if i > 0 || e.this.is_some() {
40290                self.write_space();
40291                self.write_keyword("XOR");
40292                self.write_space();
40293            }
40294            self.generate_expression(expr)?;
40295        }
40296        Ok(())
40297    }
40298
40299    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
40300        // ZIPF(this, elementcount [, gen])
40301        self.write_keyword("ZIPF");
40302        self.write("(");
40303        self.generate_expression(&e.this)?;
40304        if let Some(elementcount) = &e.elementcount {
40305            self.write(", ");
40306            self.generate_expression(elementcount)?;
40307        }
40308        if let Some(gen) = &e.gen {
40309            self.write(", ");
40310            self.generate_expression(gen)?;
40311        }
40312        self.write(")");
40313        Ok(())
40314    }
40315}
40316
40317impl Default for Generator {
40318    fn default() -> Self {
40319        Self::new()
40320    }
40321}
40322
40323#[cfg(test)]
40324mod tests {
40325    use super::*;
40326    use crate::parser::Parser;
40327
40328    fn roundtrip(sql: &str) -> String {
40329        let ast = Parser::parse_sql(sql).unwrap();
40330        Generator::sql(&ast[0]).unwrap()
40331    }
40332
40333    #[test]
40334    fn test_simple_select() {
40335        let result = roundtrip("SELECT 1");
40336        assert_eq!(result, "SELECT 1");
40337    }
40338
40339    #[test]
40340    fn test_select_from() {
40341        let result = roundtrip("SELECT a, b FROM t");
40342        assert_eq!(result, "SELECT a, b FROM t");
40343    }
40344
40345    #[test]
40346    fn test_select_where() {
40347        let result = roundtrip("SELECT * FROM t WHERE x = 1");
40348        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
40349    }
40350
40351    #[test]
40352    fn test_select_join() {
40353        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
40354        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
40355    }
40356
40357    #[test]
40358    fn test_insert() {
40359        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
40360        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
40361    }
40362
40363    #[test]
40364    fn test_pretty_print() {
40365        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
40366        let result = Generator::pretty_sql(&ast[0]).unwrap();
40367        assert!(result.contains('\n'));
40368    }
40369
40370    #[test]
40371    fn test_window_function() {
40372        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
40373        assert_eq!(
40374            result,
40375            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
40376        );
40377    }
40378
40379    #[test]
40380    fn test_window_function_with_frame() {
40381        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
40382        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
40383    }
40384
40385    #[test]
40386    fn test_aggregate_with_filter() {
40387        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
40388        assert_eq!(
40389            result,
40390            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
40391        );
40392    }
40393
40394    #[test]
40395    fn test_subscript() {
40396        let result = roundtrip("SELECT arr[0]");
40397        assert_eq!(result, "SELECT arr[0]");
40398    }
40399
40400    // DDL tests
40401    #[test]
40402    fn test_create_table() {
40403        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
40404        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
40405    }
40406
40407    #[test]
40408    fn test_create_table_with_constraints() {
40409        let result = roundtrip(
40410            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
40411        );
40412        assert_eq!(
40413            result,
40414            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
40415        );
40416    }
40417
40418    #[test]
40419    fn test_create_table_if_not_exists() {
40420        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
40421        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
40422    }
40423
40424    #[test]
40425    fn test_drop_table() {
40426        let result = roundtrip("DROP TABLE users");
40427        assert_eq!(result, "DROP TABLE users");
40428    }
40429
40430    #[test]
40431    fn test_drop_table_if_exists_cascade() {
40432        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
40433        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
40434    }
40435
40436    #[test]
40437    fn test_alter_table_add_column() {
40438        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
40439        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
40440    }
40441
40442    #[test]
40443    fn test_alter_table_drop_column() {
40444        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
40445        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
40446    }
40447
40448    #[test]
40449    fn test_create_index() {
40450        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
40451        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
40452    }
40453
40454    #[test]
40455    fn test_create_unique_index() {
40456        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
40457        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
40458    }
40459
40460    #[test]
40461    fn test_drop_index() {
40462        let result = roundtrip("DROP INDEX idx_name");
40463        assert_eq!(result, "DROP INDEX idx_name");
40464
40465        let result = roundtrip(r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#);
40466        assert_eq!(
40467            result,
40468            r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#
40469        );
40470
40471        let result = roundtrip(r#"DROP INDEX "public"."IdxMixed""#);
40472        assert_eq!(result, r#"DROP INDEX "public"."IdxMixed""#);
40473    }
40474
40475    #[test]
40476    fn test_create_view() {
40477        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
40478        assert_eq!(
40479            result,
40480            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
40481        );
40482    }
40483
40484    #[test]
40485    fn test_drop_view() {
40486        let result = roundtrip("DROP VIEW active_users");
40487        assert_eq!(result, "DROP VIEW active_users");
40488    }
40489
40490    #[test]
40491    fn test_truncate() {
40492        let result = roundtrip("TRUNCATE TABLE users");
40493        assert_eq!(result, "TRUNCATE TABLE users");
40494    }
40495
40496    #[test]
40497    fn test_string_literal_escaping_default() {
40498        // Default: double single quotes
40499        let result = roundtrip("SELECT 'hello'");
40500        assert_eq!(result, "SELECT 'hello'");
40501
40502        // Single quotes are doubled
40503        let result = roundtrip("SELECT 'it''s a test'");
40504        assert_eq!(result, "SELECT 'it''s a test'");
40505    }
40506
40507    #[test]
40508    fn test_not_in_style_prefix_default_generic() {
40509        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
40510        assert_eq!(
40511            result,
40512            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
40513        );
40514    }
40515
40516    #[test]
40517    fn test_not_in_style_infix_generic_override() {
40518        let ast =
40519            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
40520                .unwrap();
40521        let config = GeneratorConfig {
40522            not_in_style: NotInStyle::Infix,
40523            ..Default::default()
40524        };
40525        let mut gen = Generator::with_config(config);
40526        let result = gen.generate(&ast[0]).unwrap();
40527        assert_eq!(
40528            result,
40529            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
40530        );
40531    }
40532
40533    #[test]
40534    fn test_string_literal_escaping_mysql() {
40535        use crate::dialects::DialectType;
40536
40537        let config = GeneratorConfig {
40538            dialect: Some(DialectType::MySQL),
40539            ..Default::default()
40540        };
40541
40542        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
40543        let mut gen = Generator::with_config(config.clone());
40544        let result = gen.generate(&ast[0]).unwrap();
40545        assert_eq!(result, "SELECT 'hello'");
40546
40547        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
40548        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
40549        let mut gen = Generator::with_config(config.clone());
40550        let result = gen.generate(&ast[0]).unwrap();
40551        assert_eq!(result, "SELECT 'it''s'");
40552    }
40553
40554    #[test]
40555    fn test_string_literal_escaping_postgres() {
40556        use crate::dialects::DialectType;
40557
40558        let config = GeneratorConfig {
40559            dialect: Some(DialectType::PostgreSQL),
40560            ..Default::default()
40561        };
40562
40563        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
40564        let mut gen = Generator::with_config(config.clone());
40565        let result = gen.generate(&ast[0]).unwrap();
40566        assert_eq!(result, "SELECT 'hello'");
40567
40568        // PostgreSQL uses doubled quotes for regular strings
40569        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
40570        let mut gen = Generator::with_config(config.clone());
40571        let result = gen.generate(&ast[0]).unwrap();
40572        assert_eq!(result, "SELECT 'it''s'");
40573    }
40574
40575    #[test]
40576    fn test_string_literal_escaping_bigquery() {
40577        use crate::dialects::DialectType;
40578
40579        let config = GeneratorConfig {
40580            dialect: Some(DialectType::BigQuery),
40581            ..Default::default()
40582        };
40583
40584        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
40585        let mut gen = Generator::with_config(config.clone());
40586        let result = gen.generate(&ast[0]).unwrap();
40587        assert_eq!(result, "SELECT 'hello'");
40588
40589        // BigQuery escapes single quotes with backslash
40590        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
40591        let mut gen = Generator::with_config(config.clone());
40592        let result = gen.generate(&ast[0]).unwrap();
40593        assert_eq!(result, "SELECT 'it\\'s'");
40594    }
40595
40596    #[test]
40597    fn test_generate_deep_and_chain_without_stack_growth() {
40598        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
40599            Expression::column("c0"),
40600            Expression::number(0),
40601        )));
40602
40603        for i in 1..2500 {
40604            let predicate = Expression::Eq(Box::new(BinaryOp::new(
40605                Expression::column(format!("c{i}")),
40606                Expression::number(i as i64),
40607            )));
40608            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
40609        }
40610
40611        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
40612        assert!(sql.contains("c2499 = 2499"), "{}", sql);
40613    }
40614
40615    #[test]
40616    fn test_generate_deep_or_chain_without_stack_growth() {
40617        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
40618            Expression::column("c0"),
40619            Expression::number(0),
40620        )));
40621
40622        for i in 1..2500 {
40623            let predicate = Expression::Eq(Box::new(BinaryOp::new(
40624                Expression::column(format!("c{i}")),
40625                Expression::number(i as i64),
40626            )));
40627            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
40628        }
40629
40630        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
40631        assert!(sql.contains("c2499 = 2499"), "{}", sql);
40632    }
40633}