Skip to main content

polyglot_sql/
generator.rs

1//! SQL Generator -- converts an AST back into SQL strings.
2//!
3//! The central type is [`Generator`], which walks an [`Expression`] tree and
4//! emits a SQL string. Generation is controlled by a [`GeneratorConfig`] that
5//! specifies the target dialect, formatting preferences, identifier quoting
6//! style, function name casing, and many other dialect-specific flags.
7//!
8//! For one-off generation the static helpers [`Generator::sql`] and
9//! [`Generator::pretty_sql`] are the simplest entry points. For repeated
10//! generation, construct a `Generator` once with [`Generator::with_config`]
11//! and call [`Generator::generate`] for each expression.
12
13use std::borrow::Cow;
14use std::sync::Arc;
15
16use crate::error::Result;
17use crate::expressions::*;
18use crate::DialectType;
19use serde::{Deserialize, Serialize};
20
21/// SQL code generator that converts an AST (`Expression`) back into a SQL string.
22///
23/// The generator walks the expression tree and emits dialect-specific SQL text.
24/// It supports pretty-printing with configurable indentation, identifier quoting,
25/// keyword casing, function name normalization, and 30+ SQL dialect variants.
26///
27/// # Usage
28///
29/// ```rust,ignore
30/// use polyglot_sql::generator::Generator;
31/// use polyglot_sql::parser::Parser;
32///
33/// let ast = Parser::parse_sql("SELECT 1")?;
34/// // Quick one-shot generation (default config):
35/// let sql = Generator::sql(&ast[0])?;
36///
37/// // Pretty-printed output:
38/// let pretty = Generator::pretty_sql(&ast[0])?;
39///
40/// // Custom config (e.g. for a specific dialect):
41/// let config = GeneratorConfig { pretty: true, ..GeneratorConfig::default() };
42/// let mut gen = Generator::with_config(config);
43/// let sql = gen.generate(&ast[0])?;
44/// ```
45pub struct Generator {
46    config: Arc<GeneratorConfig>,
47    output: String,
48    unsupported_messages: Vec<String>,
49    indent_level: usize,
50    /// Athena dialect: true when generating Hive-style DDL (uses backticks)
51    /// false when generating Trino-style DML/CREATE VIEW (uses double quotes)
52    athena_hive_context: bool,
53    /// SQLite: column names that should have PRIMARY KEY inlined (from single-column table constraints)
54    sqlite_inline_pk_columns: std::collections::HashSet<String>,
55    /// MERGE: table name/alias qualifiers to strip from UPDATE SET left side (for PostgreSQL)
56    merge_strip_qualifiers: Vec<String>,
57    /// ClickHouse: depth counter for Nullable wrapping context in CAST types.
58    /// 0 = not in cast context, 1 = top-level cast type, 2+ = inside container type.
59    /// Positive values indicate the type should be wrapped in Nullable (for non-container types).
60    /// Negative values indicate map key context (should NOT be wrapped).
61    clickhouse_nullable_depth: i32,
62}
63
64/// Controls how SQL function names are cased in generated output.
65///
66/// - `Upper` (default) -- `COUNT`, `SUM`, `COALESCE`
67/// - `Lower` -- `count`, `sum`, `coalesce`
68/// - `None` -- preserve the original casing from the parsed input
69#[derive(Debug, Clone, Copy, PartialEq, Default)]
70pub enum NormalizeFunctions {
71    /// Emit function names in UPPER CASE (default).
72    #[default]
73    Upper,
74    /// Emit function names in lower case.
75    Lower,
76    /// Preserve the original casing from the parsed input.
77    None,
78}
79
80/// Strategy for generating row-limiting clauses across SQL dialects.
81#[derive(Debug, Clone, Copy, PartialEq, Default)]
82pub enum LimitFetchStyle {
83    /// `LIMIT n` -- MySQL, PostgreSQL, DuckDB, and most modern dialects.
84    #[default]
85    Limit,
86    /// `TOP n` -- TSQL (SQL Server).
87    Top,
88    /// `FETCH FIRST n ROWS ONLY` -- ISO/ANSI SQL standard, Oracle, DB2.
89    FetchFirst,
90}
91
92/// Strategy for rendering negated IN predicates.
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
94pub enum NotInStyle {
95    /// Emit `NOT x IN (...)` in generic mode (current compatibility behavior).
96    #[default]
97    Prefix,
98    /// Emit `x NOT IN (...)` in generic mode (canonical SQL style).
99    Infix,
100}
101
102/// Controls how the generator reacts when it encounters unsupported output.
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
104#[serde(rename_all = "lowercase")]
105pub enum UnsupportedLevel {
106    /// Ignore unsupported diagnostics and continue generation.
107    Ignore,
108    /// Collect unsupported diagnostics and continue generation.
109    #[default]
110    Warn,
111    /// Collect unsupported diagnostics and raise after generation completes.
112    Raise,
113    /// Raise immediately when the first unsupported feature is encountered.
114    Immediate,
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118enum ConnectorOperator {
119    And,
120    Or,
121}
122
123impl ConnectorOperator {
124    fn keyword(self) -> &'static str {
125        match self {
126            Self::And => "AND",
127            Self::Or => "OR",
128        }
129    }
130}
131
132/// Identifier quote style (start/end characters)
133#[derive(Debug, Clone, Copy, PartialEq)]
134pub struct IdentifierQuoteStyle {
135    /// Start character for quoting identifiers (e.g., '"', '`', '[')
136    pub start: char,
137    /// End character for quoting identifiers (e.g., '"', '`', ']')
138    pub end: char,
139}
140
141impl Default for IdentifierQuoteStyle {
142    fn default() -> Self {
143        Self {
144            start: '"',
145            end: '"',
146        }
147    }
148}
149
150impl IdentifierQuoteStyle {
151    /// Double-quote style (PostgreSQL, Oracle, standard SQL)
152    pub const DOUBLE_QUOTE: Self = Self {
153        start: '"',
154        end: '"',
155    };
156    /// Backtick style (MySQL, BigQuery, Spark, Hive)
157    pub const BACKTICK: Self = Self {
158        start: '`',
159        end: '`',
160    };
161    /// Square bracket style (TSQL, SQLite)
162    pub const BRACKET: Self = Self {
163        start: '[',
164        end: ']',
165    };
166}
167
168/// Configuration for the SQL [`Generator`].
169///
170/// This is a comprehensive port of the Python sqlglot `Generator` class attributes.
171/// It controls every aspect of SQL output: formatting, quoting, dialect-specific
172/// syntax, feature support flags, and more.
173///
174/// Most users should start from `GeneratorConfig::default()` and override only the
175/// fields they need. Dialect-specific presets are applied automatically when
176/// `dialect` is set via the higher-level transpilation API.
177///
178/// # Key fields
179///
180/// | Field | Default | Purpose |
181/// |-------|---------|---------|
182/// | `dialect` | `None` | Target SQL dialect (e.g. PostgreSQL, MySQL, BigQuery) |
183/// | `pretty` | `false` | Enable multi-line, indented output |
184/// | `indent` | `"  "` | Indentation string used when `pretty` is true |
185/// | `max_text_width` | `80` | Soft line-width limit for pretty-printing |
186/// | `normalize_functions` | `Upper` | Function name casing (`Upper`, `Lower`, `None`) |
187/// | `identifier_quote_style` | `"…"` | Quote characters for identifiers |
188/// | `uppercase_keywords` | `true` | Whether SQL keywords are upper-cased |
189#[derive(Debug, Clone)]
190pub struct GeneratorConfig {
191    // ===== Basic formatting =====
192    /// Pretty print with indentation
193    pub pretty: bool,
194    /// Indentation string (default 2 spaces)
195    pub indent: &'static str,
196    /// Maximum text width before wrapping (default 80)
197    pub max_text_width: usize,
198    /// Quote identifier style (deprecated, use identifier_quote_style instead)
199    pub identifier_quote: char,
200    /// Identifier quote style with separate start/end characters
201    pub identifier_quote_style: IdentifierQuoteStyle,
202    /// Uppercase keywords
203    pub uppercase_keywords: bool,
204    /// Normalize identifiers to lowercase when generating
205    pub normalize_identifiers: bool,
206    /// Dialect type for dialect-specific generation
207    pub dialect: Option<crate::dialects::DialectType>,
208    /// Source dialect type (used during transpilation to distinguish identity vs cross-dialect)
209    pub source_dialect: Option<crate::dialects::DialectType>,
210    /// How unsupported generation should be handled.
211    pub unsupported_level: UnsupportedLevel,
212    /// Maximum number of unsupported diagnostics to include in raised errors.
213    pub max_unsupported: usize,
214    /// How to output function names (UPPER, lower, or as-is)
215    pub normalize_functions: NormalizeFunctions,
216    /// String escape character
217    pub string_escape: char,
218    /// Whether identifiers are case-sensitive
219    pub case_sensitive_identifiers: bool,
220    /// Whether unquoted identifiers can start with a digit
221    pub identifiers_can_start_with_digit: bool,
222    /// Whether to always quote identifiers regardless of reserved keyword status
223    /// Used by dialects like Athena/Presto that prefer quoted identifiers
224    pub always_quote_identifiers: bool,
225    /// How to render negated IN predicates in generic output.
226    pub not_in_style: NotInStyle,
227
228    // ===== Null handling =====
229    /// Whether null ordering (NULLS FIRST/LAST) is supported in ORDER BY
230    /// True: Full Support, false: No support
231    pub null_ordering_supported: bool,
232    /// Whether ignore nulls is inside the agg or outside
233    /// FIRST(x IGNORE NULLS) OVER vs FIRST(x) IGNORE NULLS OVER
234    pub ignore_nulls_in_func: bool,
235    /// Whether the NVL2 function is supported
236    pub nvl2_supported: bool,
237
238    // ===== Limit/Fetch =====
239    /// How to output LIMIT clauses
240    pub limit_fetch_style: LimitFetchStyle,
241    /// Whether to generate the limit as TOP <value> instead of LIMIT <value>
242    pub limit_is_top: bool,
243    /// Whether limit and fetch allows expressions or just literals
244    pub limit_only_literals: bool,
245
246    // ===== Interval =====
247    /// Whether INTERVAL uses single quoted string ('1 day' vs 1 DAY)
248    pub single_string_interval: bool,
249    /// Whether the plural form of date parts (e.g., "days") is supported in INTERVALs
250    pub interval_allows_plural_form: bool,
251
252    // ===== CTE =====
253    /// Whether WITH RECURSIVE keyword is required (vs just WITH for recursive CTEs)
254    pub cte_recursive_keyword_required: bool,
255
256    // ===== VALUES =====
257    /// Whether VALUES can be used as a table source
258    pub values_as_table: bool,
259    /// Wrap derived values in parens (standard but Spark doesn't support)
260    pub wrap_derived_values: bool,
261
262    // ===== TABLESAMPLE =====
263    /// Keyword for TABLESAMPLE seed: "SEED" or "REPEATABLE"
264    pub tablesample_seed_keyword: &'static str,
265    /// Whether parentheses are required around the table sample's expression
266    pub tablesample_requires_parens: bool,
267    /// Whether a table sample clause's size needs to be followed by ROWS keyword
268    pub tablesample_size_is_rows: bool,
269    /// The keyword(s) to use when generating a sample clause
270    pub tablesample_keywords: &'static str,
271    /// Whether the TABLESAMPLE clause supports a method name, like BERNOULLI
272    pub tablesample_with_method: bool,
273    /// Whether the table alias comes after tablesample (Oracle, Hive)
274    pub alias_post_tablesample: bool,
275
276    // ===== Aggregate =====
277    /// Whether aggregate FILTER (WHERE ...) is supported
278    pub aggregate_filter_supported: bool,
279    /// Whether DISTINCT can be followed by multiple args in an AggFunc
280    pub multi_arg_distinct: bool,
281    /// Whether ANY/ALL quantifiers have no space before `(`: `ANY(` vs `ANY (`
282    pub quantified_no_paren_space: bool,
283    /// Whether MEDIAN(expr) is supported; if not, generates PERCENTILE_CONT
284    pub supports_median: bool,
285
286    // ===== SELECT =====
287    /// Whether SELECT ... INTO is supported
288    pub supports_select_into: bool,
289    /// Whether locking reads (SELECT ... FOR UPDATE/SHARE) are supported
290    pub locking_reads_supported: bool,
291
292    // ===== Table/Join =====
293    /// Whether a table is allowed to be renamed with a db
294    pub rename_table_with_db: bool,
295    /// Whether JOIN sides (LEFT, RIGHT) are supported with SEMI/ANTI join kinds
296    pub semi_anti_join_with_side: bool,
297    /// Whether named columns are allowed in table aliases
298    pub supports_table_alias_columns: bool,
299    /// Whether join hints should be generated
300    pub join_hints: bool,
301    /// Whether table hints should be generated
302    pub table_hints: bool,
303    /// Whether query hints should be generated
304    pub query_hints: bool,
305    /// What kind of separator to use for query hints
306    pub query_hint_sep: &'static str,
307    /// Whether Oracle-style (+) join markers are supported (Oracle, Exasol)
308    pub supports_column_join_marks: bool,
309
310    // ===== DDL =====
311    /// Whether CREATE INDEX USING method should have no space before column parens
312    /// true: `USING btree(col)`, false: `USING btree (col)`
313    pub index_using_no_space: bool,
314    /// Whether UNLOGGED tables can be created
315    pub supports_unlogged_tables: bool,
316    /// Whether CREATE TABLE LIKE statement is supported
317    pub supports_create_table_like: bool,
318    /// Whether the LikeProperty needs to be inside the schema clause
319    pub like_property_inside_schema: bool,
320    /// Whether the word COLUMN is included when adding a column with ALTER TABLE
321    pub alter_table_include_column_keyword: bool,
322    /// Whether CREATE TABLE .. COPY .. is supported (false = CLONE instead)
323    pub supports_table_copy: bool,
324    /// The syntax to use when altering the type of a column
325    pub alter_set_type: &'static str,
326    /// Whether to wrap <props> in AlterSet, e.g., ALTER ... SET (<props>)
327    pub alter_set_wrapped: bool,
328
329    // ===== Timestamp/Timezone =====
330    /// Whether TIMESTAMP WITH TIME ZONE is used (vs TIMESTAMPTZ)
331    pub tz_to_with_time_zone: bool,
332    /// Whether CONVERT_TIMEZONE() is supported
333    pub supports_convert_timezone: bool,
334
335    // ===== JSON =====
336    /// Whether the JSON extraction operators expect a value of type JSON
337    pub json_type_required_for_extraction: bool,
338    /// Whether bracketed keys like ["foo"] are supported in JSON paths
339    pub json_path_bracketed_key_supported: bool,
340    /// Whether to escape keys using single quotes in JSON paths
341    pub json_path_single_quote_escape: bool,
342    /// Whether to quote the generated expression of JsonPath
343    pub quote_json_path: bool,
344    /// What delimiter to use for separating JSON key/value pairs
345    pub json_key_value_pair_sep: &'static str,
346
347    // ===== COPY =====
348    /// Whether parameters from COPY statement are wrapped in parentheses
349    pub copy_params_are_wrapped: bool,
350    /// Whether values of params are set with "=" token or empty space
351    pub copy_params_eq_required: bool,
352    /// Whether COPY statement has INTO keyword
353    pub copy_has_into_keyword: bool,
354
355    // ===== Window functions =====
356    /// Whether EXCLUDE in window specification is supported
357    pub supports_window_exclude: bool,
358    /// UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)
359    pub unnest_with_ordinality: bool,
360    /// Whether window frame keywords (ROWS/RANGE/GROUPS, PRECEDING/FOLLOWING) should be lowercase
361    /// Exasol uses lowercase for these specific keywords
362    pub lowercase_window_frame_keywords: bool,
363    /// Whether to normalize single-bound window frames to BETWEEN form
364    /// e.g., ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
365    pub normalize_window_frame_between: bool,
366
367    // ===== Array =====
368    /// Whether ARRAY_CONCAT can be generated with varlen args
369    pub array_concat_is_var_len: bool,
370    /// Whether exp.ArraySize should generate the dimension arg too
371    /// None -> Doesn't support, false -> optional, true -> required
372    pub array_size_dim_required: Option<bool>,
373    /// Whether any(f(x) for x in array) can be implemented
374    pub can_implement_array_any: bool,
375    /// Function used for array size
376    pub array_size_name: &'static str,
377
378    // ===== BETWEEN =====
379    /// Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN
380    pub supports_between_flags: bool,
381
382    // ===== Boolean =====
383    /// Whether comparing against booleans (e.g. x IS TRUE) is supported
384    pub is_bool_allowed: bool,
385    /// Whether conditions require booleans WHERE x = 0 vs WHERE x
386    pub ensure_bools: bool,
387
388    // ===== EXTRACT =====
389    /// Whether to generate an unquoted value for EXTRACT's date part argument
390    pub extract_allows_quotes: bool,
391    /// Whether to normalize date parts in EXTRACT
392    pub normalize_extract_date_parts: bool,
393
394    // ===== Other features =====
395    /// Whether the conditional TRY(expression) function is supported
396    pub try_supported: bool,
397    /// Whether the UESCAPE syntax in unicode strings is supported
398    pub supports_uescape: bool,
399    /// Whether the function TO_NUMBER is supported
400    pub supports_to_number: bool,
401    /// Whether CONCAT requires >1 arguments
402    pub supports_single_arg_concat: bool,
403    /// Whether LAST_DAY function supports a date part argument
404    pub last_day_supports_date_part: bool,
405    /// Whether a projection can explode into multiple rows
406    pub supports_exploding_projections: bool,
407    /// Whether UNIX_SECONDS(timestamp) is supported
408    pub supports_unix_seconds: bool,
409    /// Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME
410    pub supports_like_quantifiers: bool,
411    /// Whether multi-argument DECODE(...) function is supported
412    pub supports_decode_case: bool,
413    /// Whether set op modifiers apply to the outer set op or select
414    pub set_op_modifiers: bool,
415    /// Whether FROM is supported in UPDATE statements
416    pub update_statement_supports_from: bool,
417
418    // ===== COLLATE =====
419    /// Whether COLLATE is a function instead of a binary operator
420    pub collate_is_func: bool,
421
422    // ===== INSERT =====
423    /// Whether to include "SET" keyword in "INSERT ... ON DUPLICATE KEY UPDATE"
424    pub duplicate_key_update_with_set: bool,
425    /// INSERT OVERWRITE TABLE x override
426    pub insert_overwrite: &'static str,
427
428    // ===== RETURNING =====
429    /// Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...
430    pub returning_end: bool,
431
432    // ===== MERGE =====
433    /// Whether MERGE ... WHEN MATCHED BY SOURCE is allowed
434    pub matched_by_source: bool,
435
436    // ===== CREATE FUNCTION =====
437    /// Whether create function uses an AS before the RETURN
438    pub create_function_return_as: bool,
439    /// Whether to use = instead of DEFAULT for parameter defaults (TSQL style)
440    pub parameter_default_equals: bool,
441
442    // ===== COMPUTED COLUMN =====
443    /// Whether to include the type of a computed column in the CREATE DDL
444    pub computed_column_with_type: bool,
445
446    // ===== UNPIVOT =====
447    /// Whether UNPIVOT aliases are Identifiers (false means they're Literals)
448    pub unpivot_aliases_are_identifiers: bool,
449
450    // ===== STAR =====
451    /// The keyword to use when generating a star projection with excluded columns
452    pub star_except: &'static str,
453
454    // ===== HEX =====
455    /// The HEX function name
456    pub hex_func: &'static str,
457
458    // ===== WITH =====
459    /// The keywords to use when prefixing WITH based properties
460    pub with_properties_prefix: &'static str,
461
462    // ===== PAD =====
463    /// Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional
464    pub pad_fill_pattern_is_required: bool,
465
466    // ===== INDEX =====
467    /// The string used for creating an index on a table
468    pub index_on: &'static str,
469
470    // ===== GROUPING =====
471    /// The separator for grouping sets and rollups
472    pub groupings_sep: &'static str,
473
474    // ===== STRUCT =====
475    /// Delimiters for STRUCT type
476    pub struct_delimiter: (&'static str, &'static str),
477    /// Whether Struct expressions use curly brace notation: {'key': value} (DuckDB)
478    pub struct_curly_brace_notation: bool,
479    /// Whether Array expressions omit the ARRAY keyword: [1, 2] instead of ARRAY[1, 2]
480    pub array_bracket_only: bool,
481    /// Separator between struct field name and type (": " for Hive, " " for others)
482    pub struct_field_sep: &'static str,
483
484    // ===== EXCEPT/INTERSECT =====
485    /// Whether EXCEPT and INTERSECT operations can return duplicates
486    pub except_intersect_support_all_clause: bool,
487
488    // ===== PARAMETERS/PLACEHOLDERS =====
489    /// Parameter token character (@ for TSQL, $ for PostgreSQL)
490    pub parameter_token: &'static str,
491    /// Named placeholder token (: for most, % for PostgreSQL)
492    pub named_placeholder_token: &'static str,
493
494    // ===== DATA TYPES =====
495    /// Whether data types support additional specifiers like CHAR or BYTE (oracle)
496    pub data_type_specifiers_allowed: bool,
497
498    // ===== COMMENT =====
499    /// Whether schema comments use `=` sign (COMMENT='value' vs COMMENT 'value')
500    /// StarRocks and Doris use naked COMMENT syntax without `=`
501    pub schema_comment_with_eq: bool,
502}
503
504impl Default for GeneratorConfig {
505    fn default() -> Self {
506        Self {
507            // ===== Basic formatting =====
508            pretty: false,
509            indent: "  ",
510            max_text_width: 80,
511            identifier_quote: '"',
512            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
513            uppercase_keywords: true,
514            normalize_identifiers: false,
515            dialect: None,
516            source_dialect: None,
517            unsupported_level: UnsupportedLevel::Warn,
518            max_unsupported: 3,
519            normalize_functions: NormalizeFunctions::Upper,
520            string_escape: '\'',
521            case_sensitive_identifiers: false,
522            identifiers_can_start_with_digit: false,
523            always_quote_identifiers: false,
524            not_in_style: NotInStyle::Prefix,
525
526            // ===== Null handling =====
527            null_ordering_supported: true,
528            ignore_nulls_in_func: false,
529            nvl2_supported: true,
530
531            // ===== Limit/Fetch =====
532            limit_fetch_style: LimitFetchStyle::Limit,
533            limit_is_top: false,
534            limit_only_literals: false,
535
536            // ===== Interval =====
537            single_string_interval: false,
538            interval_allows_plural_form: true,
539
540            // ===== CTE =====
541            cte_recursive_keyword_required: true,
542
543            // ===== VALUES =====
544            values_as_table: true,
545            wrap_derived_values: true,
546
547            // ===== TABLESAMPLE =====
548            tablesample_seed_keyword: "SEED",
549            tablesample_requires_parens: true,
550            tablesample_size_is_rows: true,
551            tablesample_keywords: "TABLESAMPLE",
552            tablesample_with_method: true,
553            alias_post_tablesample: false,
554
555            // ===== Aggregate =====
556            aggregate_filter_supported: true,
557            multi_arg_distinct: true,
558            quantified_no_paren_space: false,
559            supports_median: true,
560
561            // ===== SELECT =====
562            supports_select_into: false,
563            locking_reads_supported: true,
564
565            // ===== Table/Join =====
566            rename_table_with_db: true,
567            semi_anti_join_with_side: true,
568            supports_table_alias_columns: true,
569            join_hints: true,
570            table_hints: true,
571            query_hints: true,
572            query_hint_sep: ", ",
573            supports_column_join_marks: false,
574
575            // ===== DDL =====
576            index_using_no_space: false,
577            supports_unlogged_tables: false,
578            supports_create_table_like: true,
579            like_property_inside_schema: false,
580            alter_table_include_column_keyword: true,
581            supports_table_copy: true,
582            alter_set_type: "SET DATA TYPE",
583            alter_set_wrapped: false,
584
585            // ===== Timestamp/Timezone =====
586            tz_to_with_time_zone: false,
587            supports_convert_timezone: false,
588
589            // ===== JSON =====
590            json_type_required_for_extraction: false,
591            json_path_bracketed_key_supported: true,
592            json_path_single_quote_escape: false,
593            quote_json_path: true,
594            json_key_value_pair_sep: ":",
595
596            // ===== COPY =====
597            copy_params_are_wrapped: true,
598            copy_params_eq_required: false,
599            copy_has_into_keyword: true,
600
601            // ===== Window functions =====
602            supports_window_exclude: false,
603            unnest_with_ordinality: true,
604            lowercase_window_frame_keywords: false,
605            normalize_window_frame_between: false,
606
607            // ===== Array =====
608            array_concat_is_var_len: true,
609            array_size_dim_required: None,
610            can_implement_array_any: false,
611            array_size_name: "ARRAY_LENGTH",
612
613            // ===== BETWEEN =====
614            supports_between_flags: false,
615
616            // ===== Boolean =====
617            is_bool_allowed: true,
618            ensure_bools: false,
619
620            // ===== EXTRACT =====
621            extract_allows_quotes: true,
622            normalize_extract_date_parts: false,
623
624            // ===== Other features =====
625            try_supported: true,
626            supports_uescape: true,
627            supports_to_number: true,
628            supports_single_arg_concat: true,
629            last_day_supports_date_part: true,
630            supports_exploding_projections: true,
631            supports_unix_seconds: false,
632            supports_like_quantifiers: true,
633            supports_decode_case: true,
634            set_op_modifiers: true,
635            update_statement_supports_from: true,
636
637            // ===== COLLATE =====
638            collate_is_func: false,
639
640            // ===== INSERT =====
641            duplicate_key_update_with_set: true,
642            insert_overwrite: " OVERWRITE TABLE",
643
644            // ===== RETURNING =====
645            returning_end: true,
646
647            // ===== MERGE =====
648            matched_by_source: true,
649
650            // ===== CREATE FUNCTION =====
651            create_function_return_as: true,
652            parameter_default_equals: false,
653
654            // ===== COMPUTED COLUMN =====
655            computed_column_with_type: true,
656
657            // ===== UNPIVOT =====
658            unpivot_aliases_are_identifiers: true,
659
660            // ===== STAR =====
661            star_except: "EXCEPT",
662
663            // ===== HEX =====
664            hex_func: "HEX",
665
666            // ===== WITH =====
667            with_properties_prefix: "WITH",
668
669            // ===== PAD =====
670            pad_fill_pattern_is_required: false,
671
672            // ===== INDEX =====
673            index_on: "ON",
674
675            // ===== GROUPING =====
676            groupings_sep: ",",
677
678            // ===== STRUCT =====
679            struct_delimiter: ("<", ">"),
680            struct_curly_brace_notation: false,
681            array_bracket_only: false,
682            struct_field_sep: " ",
683
684            // ===== EXCEPT/INTERSECT =====
685            except_intersect_support_all_clause: true,
686
687            // ===== PARAMETERS/PLACEHOLDERS =====
688            parameter_token: "@",
689            named_placeholder_token: ":",
690
691            // ===== DATA TYPES =====
692            data_type_specifiers_allowed: false,
693
694            // ===== COMMENT =====
695            schema_comment_with_eq: true,
696        }
697    }
698}
699
700/// SQL reserved keywords that require quoting when used as identifiers
701/// Based on ANSI SQL standards and common dialect-specific reserved words
702mod reserved_keywords {
703    use std::collections::HashSet;
704    use std::sync::LazyLock;
705
706    /// Standard SQL reserved keywords (ANSI SQL:2016)
707    pub static SQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
708        [
709            "all",
710            "alter",
711            "and",
712            "any",
713            "array",
714            "as",
715            "asc",
716            "at",
717            "authorization",
718            "begin",
719            "between",
720            "both",
721            "by",
722            "case",
723            "cast",
724            "check",
725            "collate",
726            "column",
727            "commit",
728            "constraint",
729            "create",
730            "cross",
731            "cube",
732            "current",
733            "current_date",
734            "current_time",
735            "current_timestamp",
736            "current_user",
737            "default",
738            "delete",
739            "desc",
740            "distinct",
741            "drop",
742            "else",
743            "end",
744            "escape",
745            "except",
746            "execute",
747            "exists",
748            "external",
749            "false",
750            "fetch",
751            "filter",
752            "for",
753            "foreign",
754            "from",
755            "full",
756            "function",
757            "grant",
758            "group",
759            "grouping",
760            "having",
761            "if",
762            "in",
763            "index",
764            "inner",
765            "insert",
766            "intersect",
767            "interval",
768            "into",
769            "is",
770            "join",
771            "key",
772            "leading",
773            "left",
774            "like",
775            "limit",
776            "local",
777            "localtime",
778            "localtimestamp",
779            "match",
780            "merge",
781            "natural",
782            "no",
783            "not",
784            "null",
785            "of",
786            "offset",
787            "on",
788            "only",
789            "or",
790            "order",
791            "outer",
792            "over",
793            "partition",
794            "primary",
795            "procedure",
796            "range",
797            "references",
798            "right",
799            "rollback",
800            "rollup",
801            "row",
802            "rows",
803            "select",
804            "session_user",
805            "set",
806            "some",
807            "table",
808            "tablesample",
809            "then",
810            "to",
811            "trailing",
812            "true",
813            "truncate",
814            "union",
815            "unique",
816            "unknown",
817            "update",
818            "user",
819            "using",
820            "values",
821            "view",
822            "when",
823            "where",
824            "window",
825            "with",
826        ]
827        .into_iter()
828        .collect()
829    });
830
831    /// BigQuery-specific reserved keywords
832    /// Based on: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords
833    pub static BIGQUERY_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
834        let mut set = SQL_RESERVED.clone();
835        set.extend([
836            "assert_rows_modified",
837            "at",
838            "contains",
839            "cube",
840            "current",
841            "define",
842            "enum",
843            "escape",
844            "exclude",
845            "following",
846            "for",
847            "groups",
848            "hash",
849            "ignore",
850            "lateral",
851            "lookup",
852            "new",
853            "no",
854            "nulls",
855            "of",
856            "over",
857            "preceding",
858            "proto",
859            "qualify",
860            "recursive",
861            "respect",
862            "struct",
863            "tablesample",
864            "treat",
865            "unbounded",
866            "unnest",
867            "window",
868            "within",
869        ]);
870        // BigQuery does NOT reserve these keywords - they can be used as identifiers unquoted
871        set.remove("grant");
872        set.remove("key");
873        set.remove("index");
874        set.remove("offset");
875        set.remove("values");
876        set.remove("table");
877        set
878    });
879
880    /// MySQL-specific reserved keywords
881    pub static MYSQL_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
882        let mut set = SQL_RESERVED.clone();
883        set.extend([
884            "accessible",
885            "add",
886            "analyze",
887            "asensitive",
888            "before",
889            "bigint",
890            "binary",
891            "blob",
892            "call",
893            "cascade",
894            "change",
895            "char",
896            "character",
897            "condition",
898            "continue",
899            "convert",
900            "current_date",
901            "current_time",
902            "current_timestamp",
903            "current_user",
904            "cursor",
905            "database",
906            "databases",
907            "day_hour",
908            "day_microsecond",
909            "day_minute",
910            "day_second",
911            "dec",
912            "decimal",
913            "declare",
914            "delayed",
915            "describe",
916            "deterministic",
917            "distinctrow",
918            "div",
919            "double",
920            "dual",
921            "each",
922            "elseif",
923            "enclosed",
924            "escaped",
925            "exit",
926            "explain",
927            "float",
928            "float4",
929            "float8",
930            "force",
931            "get",
932            "high_priority",
933            "hour_microsecond",
934            "hour_minute",
935            "hour_second",
936            "ignore",
937            "infile",
938            "inout",
939            "insensitive",
940            "int",
941            "int1",
942            "int2",
943            "int3",
944            "int4",
945            "int8",
946            "integer",
947            "iterate",
948            "keys",
949            "kill",
950            "leave",
951            "linear",
952            "lines",
953            "load",
954            "lock",
955            "long",
956            "longblob",
957            "longtext",
958            "loop",
959            "low_priority",
960            "master_ssl_verify_server_cert",
961            "maxvalue",
962            "mediumblob",
963            "mediumint",
964            "mediumtext",
965            "middleint",
966            "minute_microsecond",
967            "minute_second",
968            "mod",
969            "modifies",
970            "no_write_to_binlog",
971            "numeric",
972            "optimize",
973            "option",
974            "optionally",
975            "out",
976            "outfile",
977            "precision",
978            "purge",
979            "read",
980            "reads",
981            "real",
982            "regexp",
983            "release",
984            "rename",
985            "repeat",
986            "replace",
987            "require",
988            "resignal",
989            "restrict",
990            "return",
991            "revoke",
992            "rlike",
993            "schema",
994            "schemas",
995            "second_microsecond",
996            "sensitive",
997            "separator",
998            "show",
999            "signal",
1000            "smallint",
1001            "spatial",
1002            "specific",
1003            "sql",
1004            "sql_big_result",
1005            "sql_calc_found_rows",
1006            "sql_small_result",
1007            "sqlexception",
1008            "sqlstate",
1009            "sqlwarning",
1010            "ssl",
1011            "starting",
1012            "straight_join",
1013            "terminated",
1014            "text",
1015            "tinyblob",
1016            "tinyint",
1017            "tinytext",
1018            "trigger",
1019            "undo",
1020            "unlock",
1021            "unsigned",
1022            "usage",
1023            "utc_date",
1024            "utc_time",
1025            "utc_timestamp",
1026            "varbinary",
1027            "varchar",
1028            "varcharacter",
1029            "varying",
1030            "while",
1031            "write",
1032            "xor",
1033            "year_month",
1034            "zerofill",
1035        ]);
1036        set.remove("table");
1037        set
1038    });
1039
1040    /// Doris-specific reserved keywords
1041    /// Extends MySQL reserved with additional Doris-specific words
1042    pub static DORIS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1043        let mut set = MYSQL_RESERVED.clone();
1044        set.extend([
1045            "aggregate",
1046            "anti",
1047            "array",
1048            "backend",
1049            "backup",
1050            "begin",
1051            "bitmap",
1052            "boolean",
1053            "broker",
1054            "buckets",
1055            "cached",
1056            "cancel",
1057            "cast",
1058            "catalog",
1059            "charset",
1060            "cluster",
1061            "collation",
1062            "columns",
1063            "comment",
1064            "commit",
1065            "config",
1066            "connection",
1067            "count",
1068            "current",
1069            "data",
1070            "date",
1071            "datetime",
1072            "day",
1073            "deferred",
1074            "distributed",
1075            "dynamic",
1076            "enable",
1077            "end",
1078            "events",
1079            "export",
1080            "external",
1081            "fields",
1082            "first",
1083            "follower",
1084            "format",
1085            "free",
1086            "frontend",
1087            "full",
1088            "functions",
1089            "global",
1090            "grants",
1091            "hash",
1092            "help",
1093            "hour",
1094            "install",
1095            "intermediate",
1096            "json",
1097            "label",
1098            "last",
1099            "less",
1100            "level",
1101            "link",
1102            "local",
1103            "location",
1104            "max",
1105            "merge",
1106            "min",
1107            "minute",
1108            "modify",
1109            "month",
1110            "name",
1111            "names",
1112            "negative",
1113            "nulls",
1114            "observer",
1115            "offset",
1116            "only",
1117            "open",
1118            "overwrite",
1119            "password",
1120            "path",
1121            "plan",
1122            "plugin",
1123            "plugins",
1124            "policy",
1125            "process",
1126            "properties",
1127            "property",
1128            "query",
1129            "quota",
1130            "recover",
1131            "refresh",
1132            "repair",
1133            "replica",
1134            "repository",
1135            "resource",
1136            "restore",
1137            "resume",
1138            "role",
1139            "roles",
1140            "rollback",
1141            "rollup",
1142            "routine",
1143            "sample",
1144            "second",
1145            "semi",
1146            "session",
1147            "signed",
1148            "snapshot",
1149            "start",
1150            "stats",
1151            "status",
1152            "stop",
1153            "stream",
1154            "string",
1155            "sum",
1156            "tables",
1157            "tablet",
1158            "temporary",
1159            "text",
1160            "timestamp",
1161            "transaction",
1162            "trash",
1163            "trim",
1164            "truncate",
1165            "type",
1166            "user",
1167            "value",
1168            "variables",
1169            "verbose",
1170            "version",
1171            "view",
1172            "warnings",
1173            "week",
1174            "work",
1175            "year",
1176        ]);
1177        set
1178    });
1179
1180    /// PostgreSQL-specific reserved keywords
1181    pub static POSTGRES_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1182        let mut set = SQL_RESERVED.clone();
1183        set.extend([
1184            "analyse",
1185            "analyze",
1186            "asymmetric",
1187            "binary",
1188            "collation",
1189            "concurrently",
1190            "current_catalog",
1191            "current_role",
1192            "current_schema",
1193            "deferrable",
1194            "do",
1195            "freeze",
1196            "ilike",
1197            "initially",
1198            "isnull",
1199            "lateral",
1200            "notnull",
1201            "placing",
1202            "returning",
1203            "similar",
1204            "symmetric",
1205            "variadic",
1206            "verbose",
1207        ]);
1208        // PostgreSQL doesn't require quoting for these keywords
1209        set.remove("default");
1210        set.remove("interval");
1211        set.remove("match");
1212        set.remove("offset");
1213        set.remove("table");
1214        set
1215    });
1216
1217    /// Redshift-specific reserved keywords
1218    /// Based on: https://docs.aws.amazon.com/redshift/latest/dg/r_pg_keywords.html
1219    /// Note: `index` is NOT reserved in Redshift (unlike PostgreSQL)
1220    pub static REDSHIFT_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1221        [
1222            "aes128",
1223            "aes256",
1224            "all",
1225            "allowoverwrite",
1226            "analyse",
1227            "analyze",
1228            "and",
1229            "any",
1230            "array",
1231            "as",
1232            "asc",
1233            "authorization",
1234            "az64",
1235            "backup",
1236            "between",
1237            "binary",
1238            "blanksasnull",
1239            "both",
1240            "bytedict",
1241            "bzip2",
1242            "case",
1243            "cast",
1244            "check",
1245            "collate",
1246            "column",
1247            "constraint",
1248            "create",
1249            "credentials",
1250            "cross",
1251            "current_date",
1252            "current_time",
1253            "current_timestamp",
1254            "current_user",
1255            "current_user_id",
1256            "default",
1257            "deferrable",
1258            "deflate",
1259            "defrag",
1260            "delta",
1261            "delta32k",
1262            "desc",
1263            "disable",
1264            "distinct",
1265            "do",
1266            "else",
1267            "emptyasnull",
1268            "enable",
1269            "encode",
1270            "encrypt",
1271            "encryption",
1272            "end",
1273            "except",
1274            "explicit",
1275            "false",
1276            "for",
1277            "foreign",
1278            "freeze",
1279            "from",
1280            "full",
1281            "globaldict256",
1282            "globaldict64k",
1283            "grant",
1284            "group",
1285            "gzip",
1286            "having",
1287            "identity",
1288            "ignore",
1289            "ilike",
1290            "in",
1291            "initially",
1292            "inner",
1293            "intersect",
1294            "interval",
1295            "into",
1296            "is",
1297            "isnull",
1298            "join",
1299            "leading",
1300            "left",
1301            "like",
1302            "limit",
1303            "localtime",
1304            "localtimestamp",
1305            "lun",
1306            "luns",
1307            "lzo",
1308            "lzop",
1309            "minus",
1310            "mostly16",
1311            "mostly32",
1312            "mostly8",
1313            "natural",
1314            "new",
1315            "not",
1316            "notnull",
1317            "null",
1318            "nulls",
1319            "off",
1320            "offline",
1321            "offset",
1322            "oid",
1323            "old",
1324            "on",
1325            "only",
1326            "open",
1327            "or",
1328            "order",
1329            "outer",
1330            "overlaps",
1331            "parallel",
1332            "partition",
1333            "percent",
1334            "permissions",
1335            "pivot",
1336            "placing",
1337            "primary",
1338            "raw",
1339            "readratio",
1340            "recover",
1341            "references",
1342            "rejectlog",
1343            "resort",
1344            "respect",
1345            "restore",
1346            "right",
1347            "select",
1348            "session_user",
1349            "similar",
1350            "snapshot",
1351            "some",
1352            "sysdate",
1353            "system",
1354            "table",
1355            "tag",
1356            "tdes",
1357            "text255",
1358            "text32k",
1359            "then",
1360            "timestamp",
1361            "to",
1362            "top",
1363            "trailing",
1364            "true",
1365            "truncatecolumns",
1366            "type",
1367            "union",
1368            "unique",
1369            "unnest",
1370            "unpivot",
1371            "user",
1372            "using",
1373            "verbose",
1374            "wallet",
1375            "when",
1376            "where",
1377            "with",
1378            "without",
1379        ]
1380        .into_iter()
1381        .collect()
1382    });
1383
1384    /// DuckDB-specific reserved keywords
1385    pub static DUCKDB_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1386        let mut set = POSTGRES_RESERVED.clone();
1387        set.extend([
1388            "anti",
1389            "asof",
1390            "columns",
1391            "describe",
1392            "groups",
1393            "macro",
1394            "pivot",
1395            "pivot_longer",
1396            "pivot_wider",
1397            "qualify",
1398            "replace",
1399            "respect",
1400            "semi",
1401            "show",
1402            "table",
1403            "unpivot",
1404        ]);
1405        set.remove("at");
1406        set.remove("key");
1407        set.remove("range");
1408        set.remove("row");
1409        set.remove("values");
1410        set
1411    });
1412
1413    /// Presto/Trino/Athena-specific reserved keywords
1414    pub static PRESTO_TRINO_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1415        let mut set = SQL_RESERVED.clone();
1416        set.extend([
1417            "alter",
1418            "and",
1419            "as",
1420            "between",
1421            "by",
1422            "case",
1423            "cast",
1424            "constraint",
1425            "create",
1426            "cross",
1427            "cube",
1428            "current_catalog",
1429            "current_date",
1430            "current_path",
1431            "current_role",
1432            "current_schema",
1433            "current_time",
1434            "current_timestamp",
1435            "current_user",
1436            "deallocate",
1437            "delete",
1438            "describe",
1439            "distinct",
1440            "drop",
1441            "else",
1442            "end",
1443            "escape",
1444            "except",
1445            "execute",
1446            "exists",
1447            "extract",
1448            "false",
1449            "for",
1450            "from",
1451            "full",
1452            "group",
1453            "grouping",
1454            "having",
1455            "in",
1456            "inner",
1457            "insert",
1458            "intersect",
1459            "into",
1460            "is",
1461            "join",
1462            "json_array",
1463            "json_exists",
1464            "json_object",
1465            "json_query",
1466            "json_table",
1467            "json_value",
1468            "left",
1469            "like",
1470            "listagg",
1471            "localtime",
1472            "localtimestamp",
1473            "natural",
1474            "normalize",
1475            "not",
1476            "null",
1477            "on",
1478            "or",
1479            "order",
1480            "outer",
1481            "prepare",
1482            "recursive",
1483            "right",
1484            "rollup",
1485            "select",
1486            "skip",
1487            "table",
1488            "then",
1489            "trim",
1490            "true",
1491            "uescape",
1492            "union",
1493            "unnest",
1494            "using",
1495            "values",
1496            "when",
1497            "where",
1498            "with",
1499        ]);
1500        // Match sqlglot behavior for Presto/Trino: KEY does not require identifier quoting.
1501        set.remove("key");
1502        set
1503    });
1504
1505    /// StarRocks-specific reserved keywords
1506    /// Based on: https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords
1507    pub static STARROCKS_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1508        [
1509            "add",
1510            "all",
1511            "alter",
1512            "analyze",
1513            "and",
1514            "array",
1515            "as",
1516            "asc",
1517            "between",
1518            "bigint",
1519            "bitmap",
1520            "both",
1521            "by",
1522            "case",
1523            "char",
1524            "character",
1525            "check",
1526            "collate",
1527            "column",
1528            "compaction",
1529            "convert",
1530            "create",
1531            "cross",
1532            "cube",
1533            "current_date",
1534            "current_role",
1535            "current_time",
1536            "current_timestamp",
1537            "current_user",
1538            "database",
1539            "databases",
1540            "decimal",
1541            "decimalv2",
1542            "decimal32",
1543            "decimal64",
1544            "decimal128",
1545            "default",
1546            "deferred",
1547            "delete",
1548            "dense_rank",
1549            "desc",
1550            "describe",
1551            "distinct",
1552            "double",
1553            "drop",
1554            "dual",
1555            "else",
1556            "except",
1557            "exists",
1558            "explain",
1559            "false",
1560            "first_value",
1561            "float",
1562            "for",
1563            "force",
1564            "from",
1565            "full",
1566            "function",
1567            "grant",
1568            "group",
1569            "grouping",
1570            "grouping_id",
1571            "groups",
1572            "having",
1573            "hll",
1574            "host",
1575            "if",
1576            "ignore",
1577            "immediate",
1578            "in",
1579            "index",
1580            "infile",
1581            "inner",
1582            "insert",
1583            "int",
1584            "integer",
1585            "intersect",
1586            "into",
1587            "is",
1588            "join",
1589            "json",
1590            "key",
1591            "keys",
1592            "kill",
1593            "lag",
1594            "largeint",
1595            "last_value",
1596            "lateral",
1597            "lead",
1598            "left",
1599            "like",
1600            "limit",
1601            "load",
1602            "localtime",
1603            "localtimestamp",
1604            "maxvalue",
1605            "minus",
1606            "mod",
1607            "not",
1608            "ntile",
1609            "null",
1610            "on",
1611            "or",
1612            "order",
1613            "outer",
1614            "outfile",
1615            "over",
1616            "partition",
1617            "percentile",
1618            "primary",
1619            "procedure",
1620            "qualify",
1621            "range",
1622            "rank",
1623            "read",
1624            "regexp",
1625            "release",
1626            "rename",
1627            "replace",
1628            "revoke",
1629            "right",
1630            "rlike",
1631            "row",
1632            "row_number",
1633            "rows",
1634            "schema",
1635            "schemas",
1636            "select",
1637            "set",
1638            "set_var",
1639            "show",
1640            "smallint",
1641            "system",
1642            "table",
1643            "terminated",
1644            "text",
1645            "then",
1646            "tinyint",
1647            "to",
1648            "true",
1649            "union",
1650            "unique",
1651            "unsigned",
1652            "update",
1653            "use",
1654            "using",
1655            "values",
1656            "varchar",
1657            "when",
1658            "where",
1659            "with",
1660        ]
1661        .into_iter()
1662        .collect()
1663    });
1664
1665    /// SingleStore-specific reserved keywords
1666    /// Based on: https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/
1667    pub static SINGLESTORE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1668        let mut set = MYSQL_RESERVED.clone();
1669        set.extend([
1670            // Additional SingleStore reserved keywords from Python sqlglot
1671            // NOTE: "all" is excluded because ORDER BY ALL needs ALL unquoted
1672            "abs",
1673            "account",
1674            "acos",
1675            "adddate",
1676            "addtime",
1677            "admin",
1678            "aes_decrypt",
1679            "aes_encrypt",
1680            "aggregate",
1681            "aggregates",
1682            "aggregator",
1683            "anti_join",
1684            "any_value",
1685            "approx_count_distinct",
1686            "approx_percentile",
1687            "arrange",
1688            "arrangement",
1689            "asin",
1690            "atan",
1691            "atan2",
1692            "attach",
1693            "autostats",
1694            "avro",
1695            "background",
1696            "backup",
1697            "batch",
1698            "batches",
1699            "boot_strapping",
1700            "ceil",
1701            "ceiling",
1702            "coercibility",
1703            "columnar",
1704            "columnstore",
1705            "compile",
1706            "concurrent",
1707            "connection_id",
1708            "cos",
1709            "cot",
1710            "current_security_groups",
1711            "current_security_roles",
1712            "dayname",
1713            "dayofmonth",
1714            "dayofweek",
1715            "dayofyear",
1716            "degrees",
1717            "dot_product",
1718            "dump",
1719            "durability",
1720            "earliest",
1721            "echo",
1722            "election",
1723            "euclidean_distance",
1724            "exp",
1725            "extractor",
1726            "extractors",
1727            "floor",
1728            "foreground",
1729            "found_rows",
1730            "from_base64",
1731            "from_days",
1732            "from_unixtime",
1733            "fs",
1734            "fulltext",
1735            "gc",
1736            "gcs",
1737            "geography",
1738            "geography_area",
1739            "geography_contains",
1740            "geography_distance",
1741            "geography_intersects",
1742            "geography_latitude",
1743            "geography_length",
1744            "geography_longitude",
1745            "geographypoint",
1746            "geography_point",
1747            "geography_within_distance",
1748            "geometry",
1749            "geometry_area",
1750            "geometry_contains",
1751            "geometry_distance",
1752            "geometry_filter",
1753            "geometry_intersects",
1754            "geometry_length",
1755            "geometrypoint",
1756            "geometry_point",
1757            "geometry_within_distance",
1758            "geometry_x",
1759            "geometry_y",
1760            "greatest",
1761            "groups",
1762            "group_concat",
1763            "gzip",
1764            "hdfs",
1765            "hex",
1766            "highlight",
1767            "ifnull",
1768            "ilike",
1769            "inet_aton",
1770            "inet_ntoa",
1771            "inet6_aton",
1772            "inet6_ntoa",
1773            "initcap",
1774            "instr",
1775            "interpreter_mode",
1776            "isnull",
1777            "json",
1778            "json_agg",
1779            "json_array_contains_double",
1780            "json_array_contains_json",
1781            "json_array_contains_string",
1782            "json_delete_key",
1783            "json_extract_double",
1784            "json_extract_json",
1785            "json_extract_string",
1786            "json_extract_bigint",
1787            "json_get_type",
1788            "json_length",
1789            "json_set_double",
1790            "json_set_json",
1791            "json_set_string",
1792            "kafka",
1793            "lag",
1794            "last_day",
1795            "last_insert_id",
1796            "latest",
1797            "lcase",
1798            "lead",
1799            "leaf",
1800            "least",
1801            "leaves",
1802            "length",
1803            "license",
1804            "links",
1805            "llvm",
1806            "ln",
1807            "load",
1808            "locate",
1809            "log",
1810            "log10",
1811            "log2",
1812            "lpad",
1813            "lz4",
1814            "management",
1815            "match",
1816            "mbc",
1817            "md5",
1818            "median",
1819            "memsql",
1820            "memsql_deserialize",
1821            "memsql_serialize",
1822            "metadata",
1823            "microsecond",
1824            "minute",
1825            "model",
1826            "monthname",
1827            "months_between",
1828            "mpl",
1829            "namespace",
1830            "node",
1831            "noparam",
1832            "now",
1833            "nth_value",
1834            "ntile",
1835            "nullcols",
1836            "nullif",
1837            "object",
1838            "octet_length",
1839            "offsets",
1840            "online",
1841            "optimizer",
1842            "orphan",
1843            "parquet",
1844            "partitions",
1845            "pause",
1846            "percentile_cont",
1847            "percentile_disc",
1848            "periodic",
1849            "persisted",
1850            "pi",
1851            "pipeline",
1852            "pipelines",
1853            "plancache",
1854            "plugins",
1855            "pool",
1856            "pools",
1857            "pow",
1858            "power",
1859            "process",
1860            "processlist",
1861            "profile",
1862            "profiles",
1863            "quarter",
1864            "queries",
1865            "query",
1866            "radians",
1867            "rand",
1868            "record",
1869            "reduce",
1870            "redundancy",
1871            "regexp_match",
1872            "regexp_substr",
1873            "remote",
1874            "replication",
1875            "resource",
1876            "resource_pool",
1877            "restore",
1878            "retry",
1879            "role",
1880            "roles",
1881            "round",
1882            "rpad",
1883            "rtrim",
1884            "running",
1885            "s3",
1886            "scalar",
1887            "sec_to_time",
1888            "second",
1889            "security_lists_intersect",
1890            "semi_join",
1891            "sha",
1892            "sha1",
1893            "sha2",
1894            "shard",
1895            "sharded",
1896            "sharded_id",
1897            "sigmoid",
1898            "sign",
1899            "sin",
1900            "skip",
1901            "sleep",
1902            "snapshot",
1903            "soname",
1904            "sparse",
1905            "spatial_check_index",
1906            "split",
1907            "sqrt",
1908            "standalone",
1909            "std",
1910            "stddev",
1911            "stddev_pop",
1912            "stddev_samp",
1913            "stop",
1914            "str_to_date",
1915            "subdate",
1916            "substr",
1917            "substring_index",
1918            "success",
1919            "synchronize",
1920            "table_checksum",
1921            "tan",
1922            "task",
1923            "timediff",
1924            "time_bucket",
1925            "time_format",
1926            "time_to_sec",
1927            "timestampadd",
1928            "timestampdiff",
1929            "to_base64",
1930            "to_char",
1931            "to_date",
1932            "to_days",
1933            "to_json",
1934            "to_number",
1935            "to_seconds",
1936            "to_timestamp",
1937            "tracelogs",
1938            "transform",
1939            "trim",
1940            "trunc",
1941            "truncate",
1942            "ucase",
1943            "unhex",
1944            "unix_timestamp",
1945            "utc_date",
1946            "utc_time",
1947            "utc_timestamp",
1948            "vacuum",
1949            "variance",
1950            "var_pop",
1951            "var_samp",
1952            "vector_sub",
1953            "voting",
1954            "week",
1955            "weekday",
1956            "weekofyear",
1957            "workload",
1958            "year",
1959        ]);
1960        // Remove "all" because ORDER BY ALL needs ALL unquoted
1961        set.remove("all");
1962        set
1963    });
1964
1965    /// SQLite-specific reserved keywords
1966    /// SQLite has a very minimal set of reserved keywords - most things can be used as identifiers unquoted
1967    /// Reference: https://www.sqlite.org/lang_keywords.html
1968    pub static SQLITE_RESERVED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1969        // SQLite only truly reserves these - everything else can be used as identifier unquoted
1970        [
1971            "abort",
1972            "action",
1973            "add",
1974            "after",
1975            "all",
1976            "alter",
1977            "always",
1978            "analyze",
1979            "and",
1980            "as",
1981            "asc",
1982            "attach",
1983            "autoincrement",
1984            "before",
1985            "begin",
1986            "between",
1987            "by",
1988            "cascade",
1989            "case",
1990            "cast",
1991            "check",
1992            "collate",
1993            "column",
1994            "commit",
1995            "conflict",
1996            "constraint",
1997            "create",
1998            "cross",
1999            "current",
2000            "current_date",
2001            "current_time",
2002            "current_timestamp",
2003            "database",
2004            "default",
2005            "deferrable",
2006            "deferred",
2007            "delete",
2008            "desc",
2009            "detach",
2010            "distinct",
2011            "do",
2012            "drop",
2013            "each",
2014            "else",
2015            "end",
2016            "escape",
2017            "except",
2018            "exclude",
2019            "exclusive",
2020            "exists",
2021            "explain",
2022            "fail",
2023            "filter",
2024            "first",
2025            "following",
2026            "for",
2027            "foreign",
2028            "from",
2029            "full",
2030            "generated",
2031            "glob",
2032            "group",
2033            "groups",
2034            "having",
2035            "if",
2036            "ignore",
2037            "immediate",
2038            "in",
2039            "index",
2040            "indexed",
2041            "initially",
2042            "inner",
2043            "insert",
2044            "instead",
2045            "intersect",
2046            "into",
2047            "is",
2048            "isnull",
2049            "join",
2050            "key",
2051            "last",
2052            "left",
2053            "like",
2054            "limit",
2055            "natural",
2056            "no",
2057            "not",
2058            "nothing",
2059            "notnull",
2060            "null",
2061            "nulls",
2062            "of",
2063            "offset",
2064            "on",
2065            "or",
2066            "order",
2067            "others",
2068            "outer",
2069            "partition",
2070            "plan",
2071            "pragma",
2072            "preceding",
2073            "primary",
2074            "query",
2075            "raise",
2076            "range",
2077            "recursive",
2078            "references",
2079            "regexp",
2080            "reindex",
2081            "release",
2082            "rename",
2083            "replace",
2084            "restrict",
2085            "returning",
2086            "right",
2087            "rollback",
2088            "row",
2089            "rows",
2090            "savepoint",
2091            "select",
2092            "set",
2093            "table",
2094            "temp",
2095            "temporary",
2096            "then",
2097            "ties",
2098            "to",
2099            "transaction",
2100            "trigger",
2101            "unbounded",
2102            "union",
2103            "unique",
2104            "update",
2105            "using",
2106            "vacuum",
2107            "values",
2108            "view",
2109            "virtual",
2110            "when",
2111            "where",
2112            "window",
2113            "with",
2114            "without",
2115        ]
2116        .into_iter()
2117        .collect()
2118    });
2119}
2120
2121impl Generator {
2122    /// Create a new generator with the default configuration.
2123    ///
2124    /// Equivalent to `Generator::with_config(GeneratorConfig::default())`.
2125    /// Uses uppercase keywords, double-quote identifier quoting, no pretty-printing,
2126    /// and no dialect-specific transformations.
2127    pub fn new() -> Self {
2128        Self::with_config(GeneratorConfig::default())
2129    }
2130
2131    /// Create a generator with a custom [`GeneratorConfig`].
2132    ///
2133    /// Use this when you need dialect-specific output, pretty-printing, or other
2134    /// non-default settings.
2135    pub fn with_config(config: GeneratorConfig) -> Self {
2136        Self::with_arc_config(Arc::new(config))
2137    }
2138
2139    /// Create a generator from a shared [`Arc<GeneratorConfig>`].
2140    ///
2141    /// This avoids cloning the configuration when multiple generators share the
2142    /// same settings (e.g. during transpilation). The [`Arc`] is cheap to clone.
2143    pub(crate) fn with_arc_config(config: Arc<GeneratorConfig>) -> Self {
2144        Self {
2145            config,
2146            output: String::new(),
2147            unsupported_messages: Vec::new(),
2148            indent_level: 0,
2149            athena_hive_context: false,
2150            sqlite_inline_pk_columns: std::collections::HashSet::new(),
2151            merge_strip_qualifiers: Vec::new(),
2152            clickhouse_nullable_depth: 0,
2153        }
2154    }
2155
2156    /// Add column aliases to a query expression for TSQL SELECT INTO.
2157    /// This ensures that unaliased columns get explicit aliases (e.g., `a` -> `a AS a`).
2158    /// Recursively processes all SELECT expressions in the query tree.
2159    fn add_column_aliases_to_query(expr: Expression) -> Expression {
2160        match expr {
2161            Expression::Select(mut select) => {
2162                // Add aliases to all select expressions that don't already have them
2163                select.expressions = select
2164                    .expressions
2165                    .into_iter()
2166                    .map(|e| Self::add_alias_to_expression(e))
2167                    .collect();
2168
2169                // Recursively process subqueries in FROM clause
2170                if let Some(ref mut from) = select.from {
2171                    from.expressions = from
2172                        .expressions
2173                        .iter()
2174                        .cloned()
2175                        .map(|e| Self::add_column_aliases_to_query(e))
2176                        .collect();
2177                }
2178
2179                Expression::Select(select)
2180            }
2181            Expression::Subquery(mut sq) => {
2182                sq.this = Self::add_column_aliases_to_query(sq.this);
2183                Expression::Subquery(sq)
2184            }
2185            Expression::Paren(mut p) => {
2186                p.this = Self::add_column_aliases_to_query(p.this);
2187                Expression::Paren(p)
2188            }
2189            // For other expressions (Union, Intersect, etc.), pass through
2190            other => other,
2191        }
2192    }
2193
2194    /// Add an alias to a single select expression if it doesn't already have one.
2195    /// Returns the expression with alias (e.g., `a` -> `a AS a`).
2196    fn add_alias_to_expression(expr: Expression) -> Expression {
2197        use crate::expressions::Alias;
2198
2199        match &expr {
2200            // Already aliased - just return it
2201            Expression::Alias(_) => expr,
2202
2203            // Column reference: add alias from column name
2204            Expression::Column(col) => Expression::Alias(Box::new(Alias {
2205                this: expr.clone(),
2206                alias: col.name.clone(),
2207                column_aliases: Vec::new(),
2208                alias_explicit_as: false,
2209                alias_keyword: None,
2210                pre_alias_comments: Vec::new(),
2211                trailing_comments: Vec::new(),
2212                inferred_type: None,
2213            })),
2214
2215            // Identifier: add alias from identifier name
2216            Expression::Identifier(ident) => Expression::Alias(Box::new(Alias {
2217                this: expr.clone(),
2218                alias: ident.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            // Subquery: recursively process and add alias if inner returns a named column
2228            Expression::Subquery(sq) => {
2229                let processed = Self::add_column_aliases_to_query(Expression::Subquery(sq.clone()));
2230                // Subqueries that are already aliased keep their alias
2231                if sq.alias.is_some() {
2232                    processed
2233                } else {
2234                    // If there's no alias, keep it as-is (let TSQL handle it)
2235                    processed
2236                }
2237            }
2238
2239            // Star expressions (*) - don't alias
2240            Expression::Star(_) => expr,
2241
2242            // For other expressions, don't add an alias
2243            // (function calls, literals, etc. would need explicit aliases anyway)
2244            _ => expr,
2245        }
2246    }
2247
2248    /// Try to evaluate a constant arithmetic expression to a number literal.
2249    /// Returns the evaluated result if the expression is a constant arithmetic expression,
2250    /// otherwise returns the original expression.
2251    fn try_evaluate_constant(expr: &Expression) -> Option<i64> {
2252        match expr {
2253            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
2254                let Literal::Number(n) = lit.as_ref() else {
2255                    unreachable!()
2256                };
2257                n.parse::<i64>().ok()
2258            }
2259            Expression::Add(op) => {
2260                let left = Self::try_evaluate_constant(&op.left)?;
2261                let right = Self::try_evaluate_constant(&op.right)?;
2262                Some(left + right)
2263            }
2264            Expression::Sub(op) => {
2265                let left = Self::try_evaluate_constant(&op.left)?;
2266                let right = Self::try_evaluate_constant(&op.right)?;
2267                Some(left - right)
2268            }
2269            Expression::Mul(op) => {
2270                let left = Self::try_evaluate_constant(&op.left)?;
2271                let right = Self::try_evaluate_constant(&op.right)?;
2272                Some(left * right)
2273            }
2274            Expression::Div(op) => {
2275                let left = Self::try_evaluate_constant(&op.left)?;
2276                let right = Self::try_evaluate_constant(&op.right)?;
2277                if right != 0 {
2278                    Some(left / right)
2279                } else {
2280                    None
2281                }
2282            }
2283            Expression::Paren(p) => Self::try_evaluate_constant(&p.this),
2284            _ => None,
2285        }
2286    }
2287
2288    /// Check if an identifier is a reserved keyword for the current dialect
2289    fn is_reserved_keyword(&self, name: &str) -> bool {
2290        use crate::dialects::DialectType;
2291        let mut buf = [0u8; 128];
2292        let lower_ref: &str = if name.len() <= 128 {
2293            for (i, b) in name.bytes().enumerate() {
2294                buf[i] = b.to_ascii_lowercase();
2295            }
2296            // SAFETY: input is valid UTF-8 and ASCII lowercase preserves that
2297            std::str::from_utf8(&buf[..name.len()]).unwrap_or(name)
2298        } else {
2299            return false;
2300        };
2301
2302        match self.config.dialect {
2303            Some(DialectType::BigQuery) => reserved_keywords::BIGQUERY_RESERVED.contains(lower_ref),
2304            Some(DialectType::MySQL) | Some(DialectType::TiDB) => {
2305                reserved_keywords::MYSQL_RESERVED.contains(lower_ref)
2306            }
2307            Some(DialectType::Doris) => reserved_keywords::DORIS_RESERVED.contains(lower_ref),
2308            Some(DialectType::SingleStore) => {
2309                reserved_keywords::SINGLESTORE_RESERVED.contains(lower_ref)
2310            }
2311            Some(DialectType::StarRocks) => {
2312                reserved_keywords::STARROCKS_RESERVED.contains(lower_ref)
2313            }
2314            Some(DialectType::PostgreSQL)
2315            | Some(DialectType::CockroachDB)
2316            | Some(DialectType::Materialize)
2317            | Some(DialectType::RisingWave) => {
2318                reserved_keywords::POSTGRES_RESERVED.contains(lower_ref)
2319            }
2320            Some(DialectType::Redshift) => reserved_keywords::REDSHIFT_RESERVED.contains(lower_ref),
2321            // Snowflake: Python sqlglot has RESERVED_KEYWORDS = set() for Snowflake,
2322            // meaning it never quotes identifiers based on reserved word status.
2323            Some(DialectType::Snowflake) => false,
2324            // ClickHouse: don't quote reserved keywords to preserve identity output
2325            Some(DialectType::ClickHouse) => false,
2326            Some(DialectType::DuckDB) => reserved_keywords::DUCKDB_RESERVED.contains(lower_ref),
2327            // Teradata: Python sqlglot has RESERVED_KEYWORDS = set() for Teradata
2328            Some(DialectType::Teradata) => false,
2329            // TSQL, Fabric, Oracle, Spark, Hive, Solr: Python sqlglot has no RESERVED_KEYWORDS for these dialects, so don't quote identifiers
2330            Some(DialectType::TSQL)
2331            | Some(DialectType::Fabric)
2332            | Some(DialectType::Oracle)
2333            | Some(DialectType::Spark)
2334            | Some(DialectType::Databricks)
2335            | Some(DialectType::Hive)
2336            | Some(DialectType::Solr) => false,
2337            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
2338                reserved_keywords::PRESTO_TRINO_RESERVED.contains(lower_ref)
2339            }
2340            Some(DialectType::SQLite) => reserved_keywords::SQLITE_RESERVED.contains(lower_ref),
2341            // For Generic dialect or None, don't add extra quoting to preserve identity
2342            Some(DialectType::Generic) | None => false,
2343            // For other dialects, use standard SQL reserved keywords
2344            _ => reserved_keywords::SQL_RESERVED.contains(lower_ref),
2345        }
2346    }
2347
2348    /// Normalize function name based on dialect settings
2349    fn normalize_func_name<'a>(&self, name: &'a str) -> Cow<'a, str> {
2350        match self.config.normalize_functions {
2351            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
2352            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
2353            NormalizeFunctions::None => Cow::Borrowed(name),
2354        }
2355    }
2356
2357    /// Generate a SQL string from an AST expression.
2358    ///
2359    /// This is the primary generation method. It clears any previous internal state,
2360    /// walks the expression tree, and returns the resulting SQL text. The output
2361    /// respects the [`GeneratorConfig`] that was supplied at construction time.
2362    ///
2363    /// The generator can be reused across multiple calls; each call to `generate`
2364    /// resets the internal buffer.
2365    pub fn generate(&mut self, expr: &Expression) -> Result<String> {
2366        self.output.clear();
2367        self.unsupported_messages.clear();
2368        self.generate_expression(expr)?;
2369        if self.config.unsupported_level == UnsupportedLevel::Raise
2370            && !self.unsupported_messages.is_empty()
2371        {
2372            return Err(crate::error::Error::generate(
2373                self.format_unsupported_messages(),
2374            ));
2375        }
2376        Ok(std::mem::take(&mut self.output))
2377    }
2378
2379    /// Returns the unsupported diagnostics collected during the most recent generate call.
2380    pub fn unsupported_messages(&self) -> &[String] {
2381        &self.unsupported_messages
2382    }
2383
2384    fn unsupported(&mut self, message: impl Into<String>) -> Result<()> {
2385        let message = message.into();
2386        if self.config.unsupported_level == UnsupportedLevel::Immediate {
2387            return Err(crate::error::Error::generate(message));
2388        }
2389        self.unsupported_messages.push(message);
2390        Ok(())
2391    }
2392
2393    fn write_unsupported_comment(&mut self, message: &str) -> Result<()> {
2394        self.unsupported(message.to_string())?;
2395        self.write("/* ");
2396        self.write(message);
2397        self.write(" */");
2398        Ok(())
2399    }
2400
2401    fn format_unsupported_messages(&self) -> String {
2402        let limit = self.config.max_unsupported.max(1);
2403        if self.unsupported_messages.len() <= limit {
2404            return self.unsupported_messages.join("; ");
2405        }
2406
2407        let mut messages = self
2408            .unsupported_messages
2409            .iter()
2410            .take(limit)
2411            .cloned()
2412            .collect::<Vec<_>>();
2413        messages.push(format!(
2414            "... and {} more",
2415            self.unsupported_messages.len() - limit
2416        ));
2417        messages.join("; ")
2418    }
2419
2420    /// Convenience: generate SQL with the default configuration (no dialect, compact output).
2421    ///
2422    /// This is a static helper that creates a throwaway `Generator` internally.
2423    /// For repeated generation, prefer constructing a `Generator` once and calling
2424    /// [`generate`](Self::generate) on it.
2425    pub fn sql(expr: &Expression) -> Result<String> {
2426        let mut gen = Generator::new();
2427        gen.generate(expr)
2428    }
2429
2430    /// Convenience: generate SQL with pretty-printing enabled (indented, multi-line).
2431    ///
2432    /// Produces human-readable output with newlines and indentation. A trailing
2433    /// semicolon is appended automatically if not already present.
2434    pub fn pretty_sql(expr: &Expression) -> Result<String> {
2435        let config = GeneratorConfig {
2436            pretty: true,
2437            ..Default::default()
2438        };
2439        let mut gen = Generator::with_config(config);
2440        let mut sql = gen.generate(expr)?;
2441        // Add semicolon for pretty output
2442        if !sql.ends_with(';') {
2443            sql.push(';');
2444        }
2445        Ok(sql)
2446    }
2447
2448    fn generate_expression(&mut self, expr: &Expression) -> Result<()> {
2449        #[cfg(feature = "stacker")]
2450        {
2451            let red_zone = if cfg!(debug_assertions) {
2452                4 * 1024 * 1024
2453            } else {
2454                1024 * 1024
2455            };
2456            stacker::maybe_grow(red_zone, 8 * 1024 * 1024, || {
2457                self.generate_expression_inner(expr)
2458            })
2459        }
2460        #[cfg(not(feature = "stacker"))]
2461        {
2462            self.generate_expression_inner(expr)
2463        }
2464    }
2465
2466    fn generate_expression_inner(&mut self, expr: &Expression) -> Result<()> {
2467        match expr {
2468            Expression::Select(select) => self.generate_select(select),
2469            Expression::Union(union) => self.generate_union(union),
2470            Expression::Intersect(intersect) => self.generate_intersect(intersect),
2471            Expression::Except(except) => self.generate_except(except),
2472            Expression::Insert(insert) => self.generate_insert(insert),
2473            Expression::Update(update) => self.generate_update(update),
2474            Expression::Delete(delete) => self.generate_delete(delete),
2475            Expression::Literal(lit) => self.generate_literal(lit),
2476            Expression::Boolean(b) => self.generate_boolean(b),
2477            Expression::Null(_) => {
2478                self.write_keyword("NULL");
2479                Ok(())
2480            }
2481            Expression::Identifier(id) => self.generate_identifier(id),
2482            Expression::Column(col) => self.generate_column(col),
2483            Expression::Pseudocolumn(pc) => self.generate_pseudocolumn(pc),
2484            Expression::Connect(c) => self.generate_connect_expr(c),
2485            Expression::Prior(p) => self.generate_prior(p),
2486            Expression::ConnectByRoot(cbr) => self.generate_connect_by_root(cbr),
2487            Expression::MatchRecognize(mr) => self.generate_match_recognize(mr),
2488            Expression::Table(table) => self.generate_table(table),
2489            Expression::StageReference(sr) => self.generate_stage_reference(sr),
2490            Expression::HistoricalData(hd) => self.generate_historical_data(hd),
2491            Expression::JoinedTable(jt) => self.generate_joined_table(jt),
2492            Expression::Star(star) => self.generate_star(star),
2493            Expression::BracedWildcard(expr) => self.generate_braced_wildcard(expr),
2494            Expression::Alias(alias) => self.generate_alias(alias),
2495            Expression::Cast(cast) => self.generate_cast(cast),
2496            Expression::Collation(coll) => self.generate_collation(coll),
2497            Expression::Case(case) => self.generate_case(case),
2498            Expression::Function(func) => self.generate_function(func),
2499            Expression::FunctionEmits(fe) => self.generate_function_emits(fe),
2500            Expression::AggregateFunction(func) => self.generate_aggregate_function(func),
2501            Expression::WindowFunction(wf) => self.generate_window_function(wf),
2502            Expression::WithinGroup(wg) => self.generate_within_group(wg),
2503            Expression::Interval(interval) => self.generate_interval(interval),
2504
2505            // String functions
2506            Expression::ConcatWs(f) => self.generate_concat_ws(f),
2507            Expression::Substring(f) => self.generate_substring(f),
2508            Expression::Upper(f) => self.generate_unary_func("UPPER", f),
2509            Expression::Lower(f) => self.generate_unary_func("LOWER", f),
2510            Expression::Length(f) => self.generate_unary_func("LENGTH", f),
2511            Expression::Trim(f) => self.generate_trim(f),
2512            Expression::LTrim(f) => self.generate_simple_func("LTRIM", &f.this),
2513            Expression::RTrim(f) => self.generate_simple_func("RTRIM", &f.this),
2514            Expression::Replace(f) => self.generate_replace(f),
2515            Expression::Reverse(f) => self.generate_simple_func("REVERSE", &f.this),
2516            Expression::Left(f) => self.generate_left_right("LEFT", f),
2517            Expression::Right(f) => self.generate_left_right("RIGHT", f),
2518            Expression::Repeat(f) => self.generate_repeat(f),
2519            Expression::Lpad(f) => self.generate_pad("LPAD", f),
2520            Expression::Rpad(f) => self.generate_pad("RPAD", f),
2521            Expression::Split(f) => self.generate_split(f),
2522            Expression::RegexpLike(f) => self.generate_regexp_like(f),
2523            Expression::RegexpReplace(f) => self.generate_regexp_replace(f),
2524            Expression::RegexpExtract(f) => self.generate_regexp_extract(f),
2525            Expression::Overlay(f) => self.generate_overlay(f),
2526
2527            // Math functions
2528            Expression::Abs(f) => self.generate_simple_func("ABS", &f.this),
2529            Expression::Round(f) => self.generate_round(f),
2530            Expression::Floor(f) => self.generate_floor(f),
2531            Expression::Ceil(f) => self.generate_ceil(f),
2532            Expression::Power(f) => self.generate_power(f),
2533            Expression::Sqrt(f) => self.generate_sqrt_cbrt(f, "SQRT", "|/"),
2534            Expression::Cbrt(f) => self.generate_sqrt_cbrt(f, "CBRT", "||/"),
2535            Expression::Ln(f) => self.generate_simple_func("LN", &f.this),
2536            Expression::Log(f) => self.generate_log(f),
2537            Expression::Exp(f) => self.generate_simple_func("EXP", &f.this),
2538            Expression::Sign(f) => self.generate_simple_func("SIGN", &f.this),
2539            Expression::Greatest(f) => self.generate_vararg_func("GREATEST", &f.expressions),
2540            Expression::Least(f) => self.generate_vararg_func("LEAST", &f.expressions),
2541
2542            // Date/time functions
2543            Expression::CurrentDate(_) => {
2544                self.write_keyword("CURRENT_DATE");
2545                Ok(())
2546            }
2547            Expression::CurrentTime(f) => self.generate_current_time(f),
2548            Expression::CurrentTimestamp(f) => self.generate_current_timestamp(f),
2549            Expression::AtTimeZone(f) => self.generate_at_time_zone(f),
2550            Expression::DateAdd(f) => self.generate_date_add(f, "DATE_ADD"),
2551            Expression::DateSub(f) => self.generate_date_add(f, "DATE_SUB"),
2552            Expression::DateDiff(f) => self.generate_datediff(f),
2553            Expression::DateTrunc(f) => self.generate_date_trunc(f),
2554            Expression::Extract(f) => self.generate_extract(f),
2555            Expression::ToDate(f) => self.generate_to_date(f),
2556            Expression::ToTimestamp(f) => self.generate_to_timestamp(f),
2557
2558            // Control flow functions
2559            Expression::Coalesce(f) => {
2560                // Use original function name if preserved (COALESCE, IFNULL)
2561                let func_name = f.original_name.as_deref().unwrap_or("COALESCE");
2562                self.generate_vararg_func(func_name, &f.expressions)
2563            }
2564            Expression::NullIf(f) => self.generate_binary_func("NULLIF", &f.this, &f.expression),
2565            Expression::IfFunc(f) => self.generate_if_func(f),
2566            Expression::IfNull(f) => self.generate_ifnull(f),
2567            Expression::Nvl(f) => self.generate_nvl(f),
2568            Expression::Nvl2(f) => self.generate_nvl2(f),
2569
2570            // Type conversion
2571            Expression::TryCast(cast) => self.generate_try_cast(cast),
2572            Expression::SafeCast(cast) => self.generate_safe_cast(cast),
2573
2574            // Typed aggregate functions
2575            Expression::Count(f) => self.generate_count(f),
2576            Expression::Sum(f) => self.generate_agg_func("SUM", f),
2577            Expression::Avg(f) => self.generate_agg_func("AVG", f),
2578            Expression::Min(f) => self.generate_agg_func("MIN", f),
2579            Expression::Max(f) => self.generate_agg_func("MAX", f),
2580            Expression::GroupConcat(f) => self.generate_group_concat(f),
2581            Expression::StringAgg(f) => self.generate_string_agg(f),
2582            Expression::ListAgg(f) => self.generate_listagg(f),
2583            Expression::ArrayAgg(f) => {
2584                // Allow cross-dialect transforms to override the function name
2585                // (e.g., COLLECT_LIST for Spark)
2586                let override_name = f
2587                    .name
2588                    .as_ref()
2589                    .filter(|n| !n.eq_ignore_ascii_case("ARRAY_AGG"))
2590                    .map(|n| n.to_ascii_uppercase());
2591                match override_name {
2592                    Some(name) => self.generate_agg_func(&name, f),
2593                    None => self.generate_agg_func("ARRAY_AGG", f),
2594                }
2595            }
2596            Expression::ArrayConcatAgg(f) => self.generate_agg_func("ARRAY_CONCAT_AGG", f),
2597            Expression::CountIf(f) => self.generate_agg_func("COUNT_IF", f),
2598            Expression::SumIf(f) => self.generate_sum_if(f),
2599            Expression::Stddev(f) => self.generate_agg_func("STDDEV", f),
2600            Expression::StddevPop(f) => self.generate_agg_func("STDDEV_POP", f),
2601            Expression::StddevSamp(f) => self.generate_stddev_samp(f),
2602            Expression::Variance(f) => self.generate_agg_func("VARIANCE", f),
2603            Expression::VarPop(f) => {
2604                let name = if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
2605                    "VARIANCE_POP"
2606                } else {
2607                    "VAR_POP"
2608                };
2609                self.generate_agg_func(name, f)
2610            }
2611            Expression::VarSamp(f) => self.generate_agg_func("VAR_SAMP", f),
2612            Expression::Skewness(f) => {
2613                let name = match self.config.dialect {
2614                    Some(DialectType::Snowflake) => "SKEW",
2615                    _ => "SKEWNESS",
2616                };
2617                self.generate_agg_func(name, f)
2618            }
2619            Expression::Median(f) => self.generate_agg_func("MEDIAN", f),
2620            Expression::Mode(f) => self.generate_agg_func("MODE", f),
2621            Expression::First(f) => self.generate_agg_func_with_ignore_nulls_bool("FIRST", f),
2622            Expression::Last(f) => self.generate_agg_func_with_ignore_nulls_bool("LAST", f),
2623            Expression::AnyValue(f) => self.generate_agg_func("ANY_VALUE", f),
2624            Expression::ApproxDistinct(f) => {
2625                match self.config.dialect {
2626                    Some(DialectType::Hive)
2627                    | Some(DialectType::Spark)
2628                    | Some(DialectType::Databricks)
2629                    | Some(DialectType::BigQuery) => {
2630                        // These dialects use APPROX_COUNT_DISTINCT (single arg only)
2631                        self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2632                    }
2633                    Some(DialectType::Redshift) => {
2634                        // Redshift uses APPROXIMATE COUNT(DISTINCT expr)
2635                        self.write_keyword("APPROXIMATE COUNT");
2636                        self.write("(");
2637                        self.write_keyword("DISTINCT");
2638                        self.write(" ");
2639                        self.generate_expression(&f.this)?;
2640                        self.write(")");
2641                        Ok(())
2642                    }
2643                    _ => self.generate_agg_func("APPROX_DISTINCT", f),
2644                }
2645            }
2646            Expression::ApproxCountDistinct(f) => {
2647                self.generate_agg_func("APPROX_COUNT_DISTINCT", f)
2648            }
2649            Expression::ApproxPercentile(f) => self.generate_approx_percentile(f),
2650            Expression::Percentile(f) => self.generate_percentile("PERCENTILE", f),
2651            Expression::LogicalAnd(f) => {
2652                let name = match self.config.dialect {
2653                    Some(DialectType::Snowflake) => "BOOLAND_AGG",
2654                    Some(DialectType::Spark)
2655                    | Some(DialectType::Databricks)
2656                    | Some(DialectType::PostgreSQL)
2657                    | Some(DialectType::DuckDB)
2658                    | Some(DialectType::Redshift) => "BOOL_AND",
2659                    Some(DialectType::Oracle)
2660                    | Some(DialectType::SQLite)
2661                    | Some(DialectType::MySQL) => "MIN",
2662                    _ => "BOOL_AND",
2663                };
2664                self.generate_agg_func(name, f)
2665            }
2666            Expression::LogicalOr(f) => {
2667                let name = match self.config.dialect {
2668                    Some(DialectType::Snowflake) => "BOOLOR_AGG",
2669                    Some(DialectType::Spark)
2670                    | Some(DialectType::Databricks)
2671                    | Some(DialectType::PostgreSQL)
2672                    | Some(DialectType::DuckDB)
2673                    | Some(DialectType::Redshift) => "BOOL_OR",
2674                    Some(DialectType::Oracle)
2675                    | Some(DialectType::SQLite)
2676                    | Some(DialectType::MySQL) => "MAX",
2677                    _ => "BOOL_OR",
2678                };
2679                self.generate_agg_func(name, f)
2680            }
2681
2682            // Typed window functions
2683            Expression::RowNumber(_) => {
2684                if self.config.dialect == Some(DialectType::ClickHouse) {
2685                    self.write("row_number");
2686                } else {
2687                    self.write_keyword("ROW_NUMBER");
2688                }
2689                self.write("()");
2690                Ok(())
2691            }
2692            Expression::Rank(r) => {
2693                self.write_keyword("RANK");
2694                self.write("(");
2695                // Oracle hypothetical rank args: RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2696                if !r.args.is_empty() {
2697                    for (i, arg) in r.args.iter().enumerate() {
2698                        if i > 0 {
2699                            self.write(", ");
2700                        }
2701                        self.generate_expression(arg)?;
2702                    }
2703                } else if let Some(order_by) = &r.order_by {
2704                    // DuckDB: RANK(ORDER BY col)
2705                    self.write_keyword(" ORDER BY ");
2706                    for (i, ob) in order_by.iter().enumerate() {
2707                        if i > 0 {
2708                            self.write(", ");
2709                        }
2710                        self.generate_ordered(ob)?;
2711                    }
2712                }
2713                self.write(")");
2714                Ok(())
2715            }
2716            Expression::DenseRank(dr) => {
2717                self.write_keyword("DENSE_RANK");
2718                self.write("(");
2719                // Oracle hypothetical rank args: DENSE_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2720                for (i, arg) in dr.args.iter().enumerate() {
2721                    if i > 0 {
2722                        self.write(", ");
2723                    }
2724                    self.generate_expression(arg)?;
2725                }
2726                self.write(")");
2727                Ok(())
2728            }
2729            Expression::NTile(f) => self.generate_ntile(f),
2730            Expression::Lead(f) => self.generate_lead_lag("LEAD", f),
2731            Expression::Lag(f) => self.generate_lead_lag("LAG", f),
2732            Expression::FirstValue(f) => {
2733                self.generate_value_func_with_ignore_nulls_bool("FIRST_VALUE", f)
2734            }
2735            Expression::LastValue(f) => {
2736                self.generate_value_func_with_ignore_nulls_bool("LAST_VALUE", f)
2737            }
2738            Expression::NthValue(f) => self.generate_nth_value(f),
2739            Expression::PercentRank(pr) => {
2740                self.write_keyword("PERCENT_RANK");
2741                self.write("(");
2742                // Oracle hypothetical rank args: PERCENT_RANK(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2743                if !pr.args.is_empty() {
2744                    for (i, arg) in pr.args.iter().enumerate() {
2745                        if i > 0 {
2746                            self.write(", ");
2747                        }
2748                        self.generate_expression(arg)?;
2749                    }
2750                } else if let Some(order_by) = &pr.order_by {
2751                    // DuckDB: PERCENT_RANK(ORDER BY col)
2752                    self.write_keyword(" ORDER BY ");
2753                    for (i, ob) in order_by.iter().enumerate() {
2754                        if i > 0 {
2755                            self.write(", ");
2756                        }
2757                        self.generate_ordered(ob)?;
2758                    }
2759                }
2760                self.write(")");
2761                Ok(())
2762            }
2763            Expression::CumeDist(cd) => {
2764                self.write_keyword("CUME_DIST");
2765                self.write("(");
2766                // Oracle hypothetical rank args: CUME_DIST(val1, val2, ...) WITHIN GROUP (ORDER BY ...)
2767                if !cd.args.is_empty() {
2768                    for (i, arg) in cd.args.iter().enumerate() {
2769                        if i > 0 {
2770                            self.write(", ");
2771                        }
2772                        self.generate_expression(arg)?;
2773                    }
2774                } else if let Some(order_by) = &cd.order_by {
2775                    // DuckDB: CUME_DIST(ORDER BY col)
2776                    self.write_keyword(" ORDER BY ");
2777                    for (i, ob) in order_by.iter().enumerate() {
2778                        if i > 0 {
2779                            self.write(", ");
2780                        }
2781                        self.generate_ordered(ob)?;
2782                    }
2783                }
2784                self.write(")");
2785                Ok(())
2786            }
2787            Expression::PercentileCont(f) => self.generate_percentile("PERCENTILE_CONT", f),
2788            Expression::PercentileDisc(f) => self.generate_percentile("PERCENTILE_DISC", f),
2789
2790            // Additional string functions
2791            Expression::Contains(f) => {
2792                self.generate_binary_func("CONTAINS", &f.this, &f.expression)
2793            }
2794            Expression::StartsWith(f) => {
2795                let name = match self.config.dialect {
2796                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "STARTSWITH",
2797                    _ => "STARTS_WITH",
2798                };
2799                self.generate_binary_func(name, &f.this, &f.expression)
2800            }
2801            Expression::EndsWith(f) => {
2802                let name = match self.config.dialect {
2803                    Some(DialectType::Snowflake) => "ENDSWITH",
2804                    Some(DialectType::Spark) | Some(DialectType::Databricks) => "ENDSWITH",
2805                    Some(DialectType::ClickHouse) => "endsWith",
2806                    _ => "ENDS_WITH",
2807                };
2808                self.generate_binary_func(name, &f.this, &f.expression)
2809            }
2810            Expression::Position(f) => self.generate_position(f),
2811            Expression::Initcap(f) => match self.config.dialect {
2812                Some(DialectType::Presto)
2813                | Some(DialectType::Trino)
2814                | Some(DialectType::Athena) => {
2815                    self.write_keyword("REGEXP_REPLACE");
2816                    self.write("(");
2817                    self.generate_expression(&f.this)?;
2818                    self.write(", '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))");
2819                    Ok(())
2820                }
2821                _ => self.generate_simple_func("INITCAP", &f.this),
2822            },
2823            Expression::Ascii(f) => self.generate_simple_func("ASCII", &f.this),
2824            Expression::Chr(f) => self.generate_simple_func("CHR", &f.this),
2825            Expression::CharFunc(f) => self.generate_char_func(f),
2826            Expression::Soundex(f) => self.generate_simple_func("SOUNDEX", &f.this),
2827            Expression::Levenshtein(f) => {
2828                self.generate_binary_func("LEVENSHTEIN", &f.this, &f.expression)
2829            }
2830
2831            // Additional math functions
2832            Expression::ModFunc(f) => self.generate_mod_func(f),
2833            Expression::Random(_) => {
2834                self.write_keyword("RANDOM");
2835                self.write("()");
2836                Ok(())
2837            }
2838            Expression::Rand(f) => self.generate_rand(f),
2839            Expression::TruncFunc(f) => self.generate_truncate_func(f),
2840            Expression::Pi(_) => {
2841                self.write_keyword("PI");
2842                self.write("()");
2843                Ok(())
2844            }
2845            Expression::Radians(f) => self.generate_simple_func("RADIANS", &f.this),
2846            Expression::Degrees(f) => self.generate_simple_func("DEGREES", &f.this),
2847            Expression::Sin(f) => self.generate_simple_func("SIN", &f.this),
2848            Expression::Cos(f) => self.generate_simple_func("COS", &f.this),
2849            Expression::Tan(f) => self.generate_simple_func("TAN", &f.this),
2850            Expression::Asin(f) => self.generate_simple_func("ASIN", &f.this),
2851            Expression::Acos(f) => self.generate_simple_func("ACOS", &f.this),
2852            Expression::Atan(f) => self.generate_simple_func("ATAN", &f.this),
2853            Expression::Atan2(f) => {
2854                let name = f.original_name.as_deref().unwrap_or("ATAN2");
2855                self.generate_binary_func(name, &f.this, &f.expression)
2856            }
2857
2858            // Control flow
2859            Expression::Decode(f) => self.generate_decode(f),
2860
2861            // Additional date/time functions
2862            Expression::DateFormat(f) => self.generate_date_format("DATE_FORMAT", f),
2863            Expression::FormatDate(f) => self.generate_date_format("FORMAT_DATE", f),
2864            Expression::Year(f) => self.generate_simple_func("YEAR", &f.this),
2865            Expression::Month(f) => self.generate_simple_func("MONTH", &f.this),
2866            Expression::Day(f) => self.generate_simple_func("DAY", &f.this),
2867            Expression::Hour(f) => self.generate_simple_func("HOUR", &f.this),
2868            Expression::Minute(f) => self.generate_simple_func("MINUTE", &f.this),
2869            Expression::Second(f) => self.generate_simple_func("SECOND", &f.this),
2870            Expression::DayOfWeek(f) => {
2871                let name = match self.config.dialect {
2872                    Some(DialectType::Presto)
2873                    | Some(DialectType::Trino)
2874                    | Some(DialectType::Athena) => "DAY_OF_WEEK",
2875                    Some(DialectType::DuckDB) => "ISODOW",
2876                    _ => "DAYOFWEEK",
2877                };
2878                self.generate_simple_func(name, &f.this)
2879            }
2880            Expression::DayOfMonth(f) => {
2881                let name = match self.config.dialect {
2882                    Some(DialectType::Presto)
2883                    | Some(DialectType::Trino)
2884                    | Some(DialectType::Athena) => "DAY_OF_MONTH",
2885                    _ => "DAYOFMONTH",
2886                };
2887                self.generate_simple_func(name, &f.this)
2888            }
2889            Expression::DayOfYear(f) => {
2890                let name = match self.config.dialect {
2891                    Some(DialectType::Presto)
2892                    | Some(DialectType::Trino)
2893                    | Some(DialectType::Athena) => "DAY_OF_YEAR",
2894                    _ => "DAYOFYEAR",
2895                };
2896                self.generate_simple_func(name, &f.this)
2897            }
2898            Expression::WeekOfYear(f) => {
2899                // Python sqlglot default is WEEK_OF_YEAR; Hive/DuckDB/Spark/MySQL override to WEEKOFYEAR
2900                let name = match self.config.dialect {
2901                    Some(DialectType::Hive)
2902                    | Some(DialectType::DuckDB)
2903                    | Some(DialectType::Spark)
2904                    | Some(DialectType::Databricks)
2905                    | Some(DialectType::MySQL) => "WEEKOFYEAR",
2906                    _ => "WEEK_OF_YEAR",
2907                };
2908                self.generate_simple_func(name, &f.this)
2909            }
2910            Expression::Quarter(f) => self.generate_simple_func("QUARTER", &f.this),
2911            Expression::AddMonths(f) => {
2912                self.generate_binary_func("ADD_MONTHS", &f.this, &f.expression)
2913            }
2914            Expression::MonthsBetween(f) => {
2915                self.generate_binary_func("MONTHS_BETWEEN", &f.this, &f.expression)
2916            }
2917            Expression::LastDay(f) => self.generate_last_day(f),
2918            Expression::NextDay(f) => self.generate_binary_func("NEXT_DAY", &f.this, &f.expression),
2919            Expression::Epoch(f) => self.generate_simple_func("EPOCH", &f.this),
2920            Expression::EpochMs(f) => self.generate_simple_func("EPOCH_MS", &f.this),
2921            Expression::FromUnixtime(f) => self.generate_from_unixtime(f),
2922            Expression::UnixTimestamp(f) => self.generate_unix_timestamp(f),
2923            Expression::MakeDate(f) => self.generate_make_date(f),
2924            Expression::MakeTimestamp(f) => self.generate_make_timestamp(f),
2925            Expression::TimestampTrunc(f) => self.generate_date_trunc(f),
2926
2927            // Array functions
2928            Expression::ArrayFunc(f) => self.generate_array_constructor(f),
2929            Expression::ArrayLength(f) => self.generate_simple_func("ARRAY_LENGTH", &f.this),
2930            Expression::ArraySize(f) => self.generate_simple_func("ARRAY_SIZE", &f.this),
2931            Expression::Cardinality(f) => self.generate_simple_func("CARDINALITY", &f.this),
2932            Expression::ArrayContains(f) => {
2933                self.generate_binary_func("ARRAY_CONTAINS", &f.this, &f.expression)
2934            }
2935            Expression::ArrayPosition(f) => {
2936                self.generate_binary_func("ARRAY_POSITION", &f.this, &f.expression)
2937            }
2938            Expression::ArrayAppend(f) => {
2939                self.generate_binary_func("ARRAY_APPEND", &f.this, &f.expression)
2940            }
2941            Expression::ArrayPrepend(f) => {
2942                self.generate_binary_func("ARRAY_PREPEND", &f.this, &f.expression)
2943            }
2944            Expression::ArrayConcat(f) => self.generate_vararg_func("ARRAY_CONCAT", &f.expressions),
2945            Expression::ArraySort(f) => self.generate_array_sort(f),
2946            Expression::ArrayReverse(f) => self.generate_simple_func("ARRAY_REVERSE", &f.this),
2947            Expression::ArrayDistinct(f) => self.generate_simple_func("ARRAY_DISTINCT", &f.this),
2948            Expression::ArrayJoin(f) => self.generate_array_join("ARRAY_JOIN", f),
2949            Expression::ArrayToString(f) => self.generate_array_join("ARRAY_TO_STRING", f),
2950            Expression::Unnest(f) => self.generate_unnest(f),
2951            Expression::Explode(f) => self.generate_simple_func("EXPLODE", &f.this),
2952            Expression::ExplodeOuter(f) => self.generate_simple_func("EXPLODE_OUTER", &f.this),
2953            Expression::ArrayFilter(f) => self.generate_array_filter(f),
2954            Expression::ArrayTransform(f) => self.generate_array_transform(f),
2955            Expression::ArrayFlatten(f) => self.generate_simple_func("FLATTEN", &f.this),
2956            Expression::ArrayCompact(f) => {
2957                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
2958                    // DuckDB: ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
2959                    self.write("LIST_FILTER(");
2960                    self.generate_expression(&f.this)?;
2961                    self.write(", _u -> NOT _u IS NULL)");
2962                    Ok(())
2963                } else {
2964                    self.generate_simple_func("ARRAY_COMPACT", &f.this)
2965                }
2966            }
2967            Expression::ArrayIntersect(f) => {
2968                let func_name = f.original_name.as_deref().unwrap_or("ARRAY_INTERSECT");
2969                self.generate_vararg_func(func_name, &f.expressions)
2970            }
2971            Expression::ArrayUnion(f) => {
2972                self.generate_binary_func("ARRAY_UNION", &f.this, &f.expression)
2973            }
2974            Expression::ArrayExcept(f) => {
2975                self.generate_binary_func("ARRAY_EXCEPT", &f.this, &f.expression)
2976            }
2977            Expression::ArrayRemove(f) => {
2978                self.generate_binary_func("ARRAY_REMOVE", &f.this, &f.expression)
2979            }
2980            Expression::ArrayZip(f) => self.generate_vararg_func("ARRAYS_ZIP", &f.expressions),
2981            Expression::Sequence(f) => self.generate_sequence("SEQUENCE", f),
2982            Expression::Generate(f) => self.generate_sequence("GENERATE_SERIES", f),
2983
2984            // Struct functions
2985            Expression::StructFunc(f) => self.generate_struct_constructor(f),
2986            Expression::StructExtract(f) => self.generate_struct_extract(f),
2987            Expression::NamedStruct(f) => self.generate_named_struct(f),
2988
2989            // Map functions
2990            Expression::MapFunc(f) => self.generate_map_constructor(f),
2991            Expression::MapFromEntries(f) => self.generate_simple_func("MAP_FROM_ENTRIES", &f.this),
2992            Expression::MapFromArrays(f) => {
2993                self.generate_binary_func("MAP_FROM_ARRAYS", &f.this, &f.expression)
2994            }
2995            Expression::MapKeys(f) => self.generate_simple_func("MAP_KEYS", &f.this),
2996            Expression::MapValues(f) => self.generate_simple_func("MAP_VALUES", &f.this),
2997            Expression::MapContainsKey(f) => {
2998                self.generate_binary_func("MAP_CONTAINS_KEY", &f.this, &f.expression)
2999            }
3000            Expression::MapConcat(f) => self.generate_vararg_func("MAP_CONCAT", &f.expressions),
3001            Expression::ElementAt(f) => {
3002                self.generate_binary_func("ELEMENT_AT", &f.this, &f.expression)
3003            }
3004            Expression::TransformKeys(f) => self.generate_transform_func("TRANSFORM_KEYS", f),
3005            Expression::TransformValues(f) => self.generate_transform_func("TRANSFORM_VALUES", f),
3006
3007            // JSON functions
3008            Expression::JsonExtract(f) => self.generate_json_extract("JSON_EXTRACT", f),
3009            Expression::JsonExtractScalar(f) => {
3010                self.generate_json_extract("JSON_EXTRACT_SCALAR", f)
3011            }
3012            Expression::JsonExtractPath(f) => self.generate_json_path("JSON_EXTRACT_PATH", f),
3013            Expression::JsonArray(f) => self.generate_vararg_func("JSON_ARRAY", &f.expressions),
3014            Expression::JsonObject(f) => self.generate_json_object(f),
3015            Expression::JsonQuery(f) => self.generate_json_extract("JSON_QUERY", f),
3016            Expression::JsonValue(f) => self.generate_json_extract("JSON_VALUE", f),
3017            Expression::JsonArrayLength(f) => {
3018                self.generate_simple_func("JSON_ARRAY_LENGTH", &f.this)
3019            }
3020            Expression::JsonKeys(f) => self.generate_simple_func("JSON_KEYS", &f.this),
3021            Expression::JsonType(f) => self.generate_simple_func("JSON_TYPE", &f.this),
3022            Expression::ParseJson(f) => {
3023                let name = match self.config.dialect {
3024                    Some(DialectType::Presto)
3025                    | Some(DialectType::Trino)
3026                    | Some(DialectType::Athena) => "JSON_PARSE",
3027                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
3028                        // PostgreSQL: CAST(x AS JSON)
3029                        self.write_keyword("CAST");
3030                        self.write("(");
3031                        self.generate_expression(&f.this)?;
3032                        self.write_keyword(" AS ");
3033                        self.write_keyword("JSON");
3034                        self.write(")");
3035                        return Ok(());
3036                    }
3037                    Some(DialectType::Hive)
3038                    | Some(DialectType::Spark)
3039                    | Some(DialectType::MySQL)
3040                    | Some(DialectType::SingleStore)
3041                    | Some(DialectType::TiDB)
3042                    | Some(DialectType::TSQL) => {
3043                        // Hive/Spark/MySQL/TSQL: just emit the string literal
3044                        self.generate_expression(&f.this)?;
3045                        return Ok(());
3046                    }
3047                    Some(DialectType::DuckDB) => "JSON",
3048                    _ => "PARSE_JSON",
3049                };
3050                self.generate_simple_func(name, &f.this)
3051            }
3052            Expression::ToJson(f) => self.generate_simple_func("TO_JSON", &f.this),
3053            Expression::JsonSet(f) => self.generate_json_modify("JSON_SET", f),
3054            Expression::JsonInsert(f) => self.generate_json_modify("JSON_INSERT", f),
3055            Expression::JsonRemove(f) => self.generate_json_path("JSON_REMOVE", f),
3056            Expression::JsonMergePatch(f) => {
3057                self.generate_binary_func("JSON_MERGE_PATCH", &f.this, &f.expression)
3058            }
3059            Expression::JsonArrayAgg(f) => self.generate_json_array_agg(f),
3060            Expression::JsonObjectAgg(f) => self.generate_json_object_agg(f),
3061
3062            // Type casting/conversion
3063            Expression::Convert(f) => self.generate_convert(f),
3064            Expression::Typeof(f) => self.generate_simple_func("TYPEOF", &f.this),
3065
3066            // Additional expressions
3067            Expression::Lambda(f) => self.generate_lambda(f),
3068            Expression::Parameter(f) => self.generate_parameter(f),
3069            Expression::Placeholder(f) => self.generate_placeholder(f),
3070            Expression::NamedArgument(f) => self.generate_named_argument(f),
3071            Expression::TableArgument(f) => self.generate_table_argument(f),
3072            Expression::SqlComment(f) => self.generate_sql_comment(f),
3073
3074            // Additional predicates
3075            Expression::NullSafeEq(op) => self.generate_null_safe_eq(op),
3076            Expression::NullSafeNeq(op) => self.generate_null_safe_neq(op),
3077            Expression::Glob(op) => self.generate_binary_op(op, "GLOB"),
3078            Expression::SimilarTo(f) => self.generate_similar_to(f),
3079            Expression::Any(f) => self.generate_quantified("ANY", f),
3080            Expression::All(f) => self.generate_quantified("ALL", f),
3081            Expression::Overlaps(f) => self.generate_overlaps(f),
3082
3083            // Bitwise operations
3084            Expression::BitwiseLeftShift(op) => {
3085                if matches!(
3086                    self.config.dialect,
3087                    Some(DialectType::Presto) | Some(DialectType::Trino)
3088                ) {
3089                    self.write_keyword("BITWISE_LEFT_SHIFT");
3090                    self.write("(");
3091                    self.generate_expression(&op.left)?;
3092                    self.write(", ");
3093                    self.generate_expression(&op.right)?;
3094                    self.write(")");
3095                    Ok(())
3096                } else if matches!(
3097                    self.config.dialect,
3098                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3099                ) {
3100                    self.write_keyword("SHIFTLEFT");
3101                    self.write("(");
3102                    self.generate_expression(&op.left)?;
3103                    self.write(", ");
3104                    self.generate_expression(&op.right)?;
3105                    self.write(")");
3106                    Ok(())
3107                } else {
3108                    self.generate_binary_op(op, "<<")
3109                }
3110            }
3111            Expression::BitwiseRightShift(op) => {
3112                if matches!(
3113                    self.config.dialect,
3114                    Some(DialectType::Presto) | Some(DialectType::Trino)
3115                ) {
3116                    self.write_keyword("BITWISE_RIGHT_SHIFT");
3117                    self.write("(");
3118                    self.generate_expression(&op.left)?;
3119                    self.write(", ");
3120                    self.generate_expression(&op.right)?;
3121                    self.write(")");
3122                    Ok(())
3123                } else if matches!(
3124                    self.config.dialect,
3125                    Some(DialectType::Spark) | Some(DialectType::Databricks)
3126                ) {
3127                    self.write_keyword("SHIFTRIGHT");
3128                    self.write("(");
3129                    self.generate_expression(&op.left)?;
3130                    self.write(", ");
3131                    self.generate_expression(&op.right)?;
3132                    self.write(")");
3133                    Ok(())
3134                } else {
3135                    self.generate_binary_op(op, ">>")
3136                }
3137            }
3138            Expression::BitwiseAndAgg(f) => self.generate_agg_func("BIT_AND", f),
3139            Expression::BitwiseOrAgg(f) => self.generate_agg_func("BIT_OR", f),
3140            Expression::BitwiseXorAgg(f) => self.generate_agg_func("BIT_XOR", f),
3141
3142            // Array/struct/map access
3143            Expression::Subscript(s) => self.generate_subscript(s),
3144            Expression::Dot(d) => self.generate_dot_access(d),
3145            Expression::MethodCall(m) => self.generate_method_call(m),
3146            Expression::ArraySlice(s) => self.generate_array_slice(s),
3147
3148            Expression::And(op) => self.generate_connector_op(op, ConnectorOperator::And),
3149            Expression::Or(op) => self.generate_connector_op(op, ConnectorOperator::Or),
3150            Expression::Add(op) => self.generate_binary_op(op, "+"),
3151            Expression::Sub(op) => self.generate_binary_op(op, "-"),
3152            Expression::Mul(op) => self.generate_binary_op(op, "*"),
3153            Expression::Div(op) => self.generate_binary_op(op, "/"),
3154            Expression::IntDiv(f) => {
3155                use crate::dialects::DialectType;
3156                if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
3157                    // DuckDB uses // operator for integer division
3158                    self.generate_expression(&f.this)?;
3159                    self.write(" // ");
3160                    self.generate_expression(&f.expression)?;
3161                    Ok(())
3162                } else if matches!(
3163                    self.config.dialect,
3164                    Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
3165                ) {
3166                    // Hive/Spark use DIV as an infix operator
3167                    self.generate_expression(&f.this)?;
3168                    self.write(" ");
3169                    self.write_keyword("DIV");
3170                    self.write(" ");
3171                    self.generate_expression(&f.expression)?;
3172                    Ok(())
3173                } else {
3174                    // Other dialects use DIV function
3175                    self.write_keyword("DIV");
3176                    self.write("(");
3177                    self.generate_expression(&f.this)?;
3178                    self.write(", ");
3179                    self.generate_expression(&f.expression)?;
3180                    self.write(")");
3181                    Ok(())
3182                }
3183            }
3184            Expression::Mod(op) => {
3185                if matches!(self.config.dialect, Some(DialectType::Teradata)) {
3186                    self.generate_binary_op(op, "MOD")
3187                } else {
3188                    self.generate_binary_op(op, "%")
3189                }
3190            }
3191            Expression::Eq(op) => self.generate_binary_op(op, "="),
3192            Expression::Neq(op) => self.generate_binary_op(op, "<>"),
3193            Expression::Lt(op) => self.generate_binary_op(op, "<"),
3194            Expression::Lte(op) => self.generate_binary_op(op, "<="),
3195            Expression::Gt(op) => self.generate_binary_op(op, ">"),
3196            Expression::Gte(op) => self.generate_binary_op(op, ">="),
3197            Expression::Like(op) => self.generate_like_op(op, "LIKE"),
3198            Expression::ILike(op) => self.generate_like_op(op, "ILIKE"),
3199            Expression::Match(op) => self.generate_binary_op(op, "MATCH"),
3200            Expression::Concat(op) => {
3201                // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
3202                if self.config.dialect == Some(DialectType::Solr) {
3203                    self.generate_binary_op(op, "OR")
3204                } else if self.config.dialect == Some(DialectType::MySQL) {
3205                    self.generate_mysql_concat_from_concat(op)
3206                } else {
3207                    self.generate_binary_op(op, "||")
3208                }
3209            }
3210            Expression::BitwiseAnd(op) => {
3211                // Presto/Trino use BITWISE_AND function
3212                if matches!(
3213                    self.config.dialect,
3214                    Some(DialectType::Presto) | Some(DialectType::Trino)
3215                ) {
3216                    self.write_keyword("BITWISE_AND");
3217                    self.write("(");
3218                    self.generate_expression(&op.left)?;
3219                    self.write(", ");
3220                    self.generate_expression(&op.right)?;
3221                    self.write(")");
3222                    Ok(())
3223                } else {
3224                    self.generate_binary_op(op, "&")
3225                }
3226            }
3227            Expression::BitwiseOr(op) => {
3228                // Presto/Trino use BITWISE_OR function
3229                if matches!(
3230                    self.config.dialect,
3231                    Some(DialectType::Presto) | Some(DialectType::Trino)
3232                ) {
3233                    self.write_keyword("BITWISE_OR");
3234                    self.write("(");
3235                    self.generate_expression(&op.left)?;
3236                    self.write(", ");
3237                    self.generate_expression(&op.right)?;
3238                    self.write(")");
3239                    Ok(())
3240                } else {
3241                    self.generate_binary_op(op, "|")
3242                }
3243            }
3244            Expression::BitwiseXor(op) => {
3245                // Presto/Trino use BITWISE_XOR function, PostgreSQL uses #, others use ^
3246                if matches!(
3247                    self.config.dialect,
3248                    Some(DialectType::Presto) | Some(DialectType::Trino)
3249                ) {
3250                    self.write_keyword("BITWISE_XOR");
3251                    self.write("(");
3252                    self.generate_expression(&op.left)?;
3253                    self.write(", ");
3254                    self.generate_expression(&op.right)?;
3255                    self.write(")");
3256                    Ok(())
3257                } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
3258                    self.generate_binary_op(op, "#")
3259                } else {
3260                    self.generate_binary_op(op, "^")
3261                }
3262            }
3263            Expression::Adjacent(op) => self.generate_binary_op(op, "-|-"),
3264            Expression::TsMatch(op) => self.generate_binary_op(op, "@@"),
3265            Expression::PropertyEQ(op) => self.generate_binary_op(op, ":="),
3266            Expression::ArrayContainsAll(op) => self.generate_binary_op(op, "@>"),
3267            Expression::ArrayContainedBy(op) => self.generate_binary_op(op, "<@"),
3268            Expression::ArrayOverlaps(op) => self.generate_binary_op(op, "&&"),
3269            Expression::JSONBContainsAllTopKeys(op) => self.generate_binary_op(op, "?&"),
3270            Expression::JSONBContainsAnyTopKeys(op) => self.generate_binary_op(op, "?|"),
3271            Expression::JSONBContains(f) => {
3272                // PostgreSQL JSONB contains key operator: a ? b
3273                self.generate_expression(&f.this)?;
3274                self.write_space();
3275                self.write("?");
3276                self.write_space();
3277                self.generate_expression(&f.expression)
3278            }
3279            Expression::JSONBDeleteAtPath(op) => self.generate_binary_op(op, "#-"),
3280            Expression::ExtendsLeft(op) => self.generate_binary_op(op, "&<"),
3281            Expression::ExtendsRight(op) => self.generate_binary_op(op, "&>"),
3282            Expression::Not(op) => match &op.this {
3283                Expression::Like(like) => self.generate_like_op_negated(like, "LIKE"),
3284                Expression::ILike(like) => self.generate_like_op_negated(like, "ILIKE"),
3285                _ => self.generate_unary_op(op, "NOT"),
3286            },
3287            Expression::Neg(op) => self.generate_unary_op(op, "-"),
3288            Expression::BitwiseNot(op) => {
3289                // Presto/Trino use BITWISE_NOT function
3290                if matches!(
3291                    self.config.dialect,
3292                    Some(DialectType::Presto) | Some(DialectType::Trino)
3293                ) {
3294                    self.write_keyword("BITWISE_NOT");
3295                    self.write("(");
3296                    self.generate_expression(&op.this)?;
3297                    self.write(")");
3298                    Ok(())
3299                } else {
3300                    self.generate_unary_op(op, "~")
3301                }
3302            }
3303            Expression::In(in_expr) => self.generate_in(in_expr),
3304            Expression::Between(between) => self.generate_between(between),
3305            Expression::IsNull(is_null) => self.generate_is_null(is_null),
3306            Expression::IsTrue(is_true) => self.generate_is_true(is_true),
3307            Expression::IsFalse(is_false) => self.generate_is_false(is_false),
3308            Expression::IsJson(is_json) => self.generate_is_json(is_json),
3309            Expression::Is(is_expr) => self.generate_is(is_expr),
3310            Expression::Exists(exists) => self.generate_exists(exists),
3311            Expression::MemberOf(member_of) => self.generate_member_of(member_of),
3312            Expression::Subquery(subquery) => self.generate_subquery(subquery),
3313            Expression::Paren(paren) => {
3314                // JoinedTable already outputs its own parentheses, so don't double-wrap
3315                let skip_parens = matches!(&paren.this, Expression::JoinedTable(_));
3316
3317                if !skip_parens {
3318                    self.write("(");
3319                    if self.config.pretty {
3320                        self.write_newline();
3321                        self.indent_level += 1;
3322                        self.write_indent();
3323                    }
3324                }
3325                self.generate_expression(&paren.this)?;
3326                if !skip_parens {
3327                    if self.config.pretty {
3328                        self.write_newline();
3329                        self.indent_level -= 1;
3330                        self.write_indent();
3331                    }
3332                    self.write(")");
3333                }
3334                // Output trailing comments after closing paren
3335                for comment in &paren.trailing_comments {
3336                    self.write(" ");
3337                    self.write_formatted_comment(comment);
3338                }
3339                Ok(())
3340            }
3341            Expression::Array(arr) => self.generate_array(arr),
3342            Expression::Tuple(tuple) => self.generate_tuple(tuple),
3343            Expression::PipeOperator(pipe) => self.generate_pipe_operator(pipe),
3344            Expression::Ordered(ordered) => self.generate_ordered(ordered),
3345            Expression::DataType(dt) => self.generate_data_type(dt),
3346            Expression::Raw(raw) => {
3347                self.write(&raw.sql);
3348                Ok(())
3349            }
3350            Expression::CreateTask(task) => self.generate_create_task(task),
3351            Expression::TryCatch(try_catch) => self.generate_try_catch(try_catch),
3352            Expression::Command(cmd) => {
3353                self.write(&cmd.this);
3354                Ok(())
3355            }
3356            Expression::Kill(kill) => {
3357                self.write_keyword("KILL");
3358                if let Some(kind) = &kill.kind {
3359                    self.write_space();
3360                    self.write_keyword(kind);
3361                }
3362                self.write_space();
3363                self.generate_expression(&kill.this)?;
3364                Ok(())
3365            }
3366            Expression::Prepare(prepare) => self.generate_prepare(prepare),
3367            Expression::Execute(exec) => {
3368                self.write_keyword("EXECUTE");
3369                self.write_space();
3370                self.generate_expression(&exec.this)?;
3371                if exec.prepared {
3372                    if !exec.arguments.is_empty() {
3373                        self.write("(");
3374                        for (i, argument) in exec.arguments.iter().enumerate() {
3375                            if i > 0 {
3376                                self.write(", ");
3377                            }
3378                            self.generate_expression(argument)?;
3379                        }
3380                        self.write(")");
3381                    }
3382                    return Ok(());
3383                }
3384                for (i, param) in exec.parameters.iter().enumerate() {
3385                    if i == 0 {
3386                        self.write_space();
3387                    } else {
3388                        self.write(", ");
3389                    }
3390                    self.write(&param.name);
3391                    // Only write = value for named parameters (not positional)
3392                    if !param.positional {
3393                        self.write(" = ");
3394                        self.generate_expression(&param.value)?;
3395                    }
3396                    if param.output {
3397                        self.write_space();
3398                        self.write_keyword("OUTPUT");
3399                    }
3400                }
3401                if let Some(ref suffix) = exec.suffix {
3402                    self.write_space();
3403                    self.write(suffix);
3404                }
3405                Ok(())
3406            }
3407            Expression::Annotated(annotated) => {
3408                self.generate_expression(&annotated.this)?;
3409                for comment in &annotated.trailing_comments {
3410                    self.write(" ");
3411                    self.write_formatted_comment(comment);
3412                }
3413                Ok(())
3414            }
3415
3416            // DDL statements
3417            Expression::CreateTable(ct) => self.generate_create_table(ct),
3418            Expression::DropTable(dt) => self.generate_drop_table(dt),
3419            Expression::Undrop(u) => self.generate_undrop(u),
3420            Expression::AlterTable(at) => self.generate_alter_table(at),
3421            Expression::CreateIndex(ci) => self.generate_create_index(ci),
3422            Expression::DropIndex(di) => self.generate_drop_index(di),
3423            Expression::CreateView(cv) => self.generate_create_view(cv),
3424            Expression::DropView(dv) => self.generate_drop_view(dv),
3425            Expression::AlterView(av) => self.generate_alter_view(av),
3426            Expression::AlterIndex(ai) => self.generate_alter_index(ai),
3427            Expression::Truncate(tr) => self.generate_truncate(tr),
3428            Expression::Use(u) => self.generate_use(u),
3429            // Phase 4: Additional DDL statements
3430            Expression::CreateSchema(cs) => self.generate_create_schema(cs),
3431            Expression::DropSchema(ds) => self.generate_drop_schema(ds),
3432            Expression::DropNamespace(dn) => self.generate_drop_namespace(dn),
3433            Expression::CreateDatabase(cd) => self.generate_create_database(cd),
3434            Expression::DropDatabase(dd) => self.generate_drop_database(dd),
3435            Expression::CreateFunction(cf) => self.generate_create_function(cf),
3436            Expression::DropFunction(df) => self.generate_drop_function(df),
3437            Expression::CreateProcedure(cp) => self.generate_create_procedure(cp),
3438            Expression::DropProcedure(dp) => self.generate_drop_procedure(dp),
3439            Expression::CreateSequence(cs) => self.generate_create_sequence(cs),
3440            Expression::CreateSynonym(cs) => {
3441                self.write_keyword("CREATE SYNONYM");
3442                self.write_space();
3443                self.generate_table(&cs.name)?;
3444                self.write_space();
3445                self.write_keyword("FOR");
3446                self.write_space();
3447                self.generate_table(&cs.target)?;
3448                Ok(())
3449            }
3450            Expression::DropSequence(ds) => self.generate_drop_sequence(ds),
3451            Expression::AlterSequence(als) => self.generate_alter_sequence(als),
3452            Expression::CreateTrigger(ct) => self.generate_create_trigger(ct),
3453            Expression::DropTrigger(dt) => self.generate_drop_trigger(dt),
3454            Expression::CreateType(ct) => self.generate_create_type(ct),
3455            Expression::DropType(dt) => self.generate_drop_type(dt),
3456            Expression::Describe(d) => self.generate_describe(d),
3457            Expression::Show(s) => self.generate_show(s),
3458
3459            // CACHE/UNCACHE/LOAD TABLE (Spark/Hive)
3460            Expression::Cache(c) => self.generate_cache(c),
3461            Expression::Uncache(u) => self.generate_uncache(u),
3462            Expression::LoadData(l) => self.generate_load_data(l),
3463            Expression::Pragma(p) => self.generate_pragma(p),
3464            Expression::Grant(g) => self.generate_grant(g),
3465            Expression::Revoke(r) => self.generate_revoke(r),
3466            Expression::Comment(c) => self.generate_comment(c),
3467            Expression::SetStatement(s) => self.generate_set_statement(s),
3468
3469            // PIVOT/UNPIVOT
3470            Expression::Pivot(pivot) => self.generate_pivot(pivot),
3471            Expression::Unpivot(unpivot) => self.generate_unpivot(unpivot),
3472
3473            // VALUES table constructor
3474            Expression::Values(values) => self.generate_values(values),
3475
3476            // === BATCH-GENERATED MATCH ARMS (481 variants) ===
3477            Expression::AIAgg(e) => self.generate_ai_agg(e),
3478            Expression::AIClassify(e) => self.generate_ai_classify(e),
3479            Expression::AddPartition(e) => self.generate_add_partition(e),
3480            Expression::AlgorithmProperty(e) => self.generate_algorithm_property(e),
3481            Expression::Aliases(e) => self.generate_aliases(e),
3482            Expression::AllowedValuesProperty(e) => self.generate_allowed_values_property(e),
3483            Expression::AlterColumn(e) => self.generate_alter_column(e),
3484            Expression::AlterSession(e) => self.generate_alter_session(e),
3485            Expression::AlterSet(e) => self.generate_alter_set(e),
3486            Expression::AlterSortKey(e) => self.generate_alter_sort_key(e),
3487            Expression::Analyze(e) => self.generate_analyze(e),
3488            Expression::AnalyzeDelete(e) => self.generate_analyze_delete(e),
3489            Expression::AnalyzeHistogram(e) => self.generate_analyze_histogram(e),
3490            Expression::AnalyzeListChainedRows(e) => self.generate_analyze_list_chained_rows(e),
3491            Expression::AnalyzeSample(e) => self.generate_analyze_sample(e),
3492            Expression::AnalyzeStatistics(e) => self.generate_analyze_statistics(e),
3493            Expression::AnalyzeValidate(e) => self.generate_analyze_validate(e),
3494            Expression::AnalyzeWith(e) => self.generate_analyze_with(e),
3495            Expression::Anonymous(e) => self.generate_anonymous(e),
3496            Expression::AnonymousAggFunc(e) => self.generate_anonymous_agg_func(e),
3497            Expression::Apply(e) => self.generate_apply(e),
3498            Expression::ApproxPercentileEstimate(e) => self.generate_approx_percentile_estimate(e),
3499            Expression::ApproxQuantile(e) => self.generate_approx_quantile(e),
3500            Expression::ApproxQuantiles(e) => self.generate_approx_quantiles(e),
3501            Expression::ApproxTopK(e) => self.generate_approx_top_k(e),
3502            Expression::ApproxTopKAccumulate(e) => self.generate_approx_top_k_accumulate(e),
3503            Expression::ApproxTopKCombine(e) => self.generate_approx_top_k_combine(e),
3504            Expression::ApproxTopKEstimate(e) => self.generate_approx_top_k_estimate(e),
3505            Expression::ApproxTopSum(e) => self.generate_approx_top_sum(e),
3506            Expression::ArgMax(e) => self.generate_arg_max(e),
3507            Expression::ArgMin(e) => self.generate_arg_min(e),
3508            Expression::ArrayAll(e) => self.generate_array_all(e),
3509            Expression::ArrayAny(e) => self.generate_array_any(e),
3510            Expression::ArrayConstructCompact(e) => self.generate_array_construct_compact(e),
3511            Expression::ArraySum(e) => self.generate_array_sum(e),
3512            Expression::AtIndex(e) => self.generate_at_index(e),
3513            Expression::Attach(e) => self.generate_attach(e),
3514            Expression::AttachOption(e) => self.generate_attach_option(e),
3515            Expression::AutoIncrementProperty(e) => self.generate_auto_increment_property(e),
3516            Expression::AutoRefreshProperty(e) => self.generate_auto_refresh_property(e),
3517            Expression::BackupProperty(e) => self.generate_backup_property(e),
3518            Expression::Base64DecodeBinary(e) => self.generate_base64_decode_binary(e),
3519            Expression::Base64DecodeString(e) => self.generate_base64_decode_string(e),
3520            Expression::Base64Encode(e) => self.generate_base64_encode(e),
3521            Expression::BlockCompressionProperty(e) => self.generate_block_compression_property(e),
3522            Expression::Booland(e) => self.generate_booland(e),
3523            Expression::Boolor(e) => self.generate_boolor(e),
3524            Expression::BuildProperty(e) => self.generate_build_property(e),
3525            Expression::ByteString(e) => self.generate_byte_string(e),
3526            Expression::CaseSpecificColumnConstraint(e) => {
3527                self.generate_case_specific_column_constraint(e)
3528            }
3529            Expression::CastToStrType(e) => self.generate_cast_to_str_type(e),
3530            Expression::Changes(e) => self.generate_changes(e),
3531            Expression::CharacterSetColumnConstraint(e) => {
3532                self.generate_character_set_column_constraint(e)
3533            }
3534            Expression::CharacterSetProperty(e) => self.generate_character_set_property(e),
3535            Expression::CheckColumnConstraint(e) => self.generate_check_column_constraint(e),
3536            Expression::AssumeColumnConstraint(e) => self.generate_assume_column_constraint(e),
3537            Expression::CheckJson(e) => self.generate_check_json(e),
3538            Expression::CheckXml(e) => self.generate_check_xml(e),
3539            Expression::ChecksumProperty(e) => self.generate_checksum_property(e),
3540            Expression::Clone(e) => self.generate_clone(e),
3541            Expression::ClusterBy(e) => self.generate_cluster_by(e),
3542            Expression::ClusterByColumnsProperty(e) => self.generate_cluster_by_columns_property(e),
3543            Expression::ClusteredByProperty(e) => self.generate_clustered_by_property(e),
3544            Expression::CollateProperty(e) => self.generate_collate_property(e),
3545            Expression::ColumnConstraint(e) => self.generate_column_constraint(e),
3546            Expression::ColumnDef(e) => self.generate_column_def_expr(e),
3547            Expression::ColumnPosition(e) => self.generate_column_position(e),
3548            Expression::ColumnPrefix(e) => self.generate_column_prefix(e),
3549            Expression::Columns(e) => self.generate_columns(e),
3550            Expression::CombinedAggFunc(e) => self.generate_combined_agg_func(e),
3551            Expression::CombinedParameterizedAgg(e) => self.generate_combined_parameterized_agg(e),
3552            Expression::Commit(e) => self.generate_commit(e),
3553            Expression::Comprehension(e) => self.generate_comprehension(e),
3554            Expression::Compress(e) => self.generate_compress(e),
3555            Expression::CompressColumnConstraint(e) => self.generate_compress_column_constraint(e),
3556            Expression::ComputedColumnConstraint(e) => self.generate_computed_column_constraint(e),
3557            Expression::ConditionalInsert(e) => self.generate_conditional_insert(e),
3558            Expression::Constraint(e) => self.generate_constraint(e),
3559            Expression::ConvertTimezone(e) => self.generate_convert_timezone(e),
3560            Expression::ConvertToCharset(e) => self.generate_convert_to_charset(e),
3561            Expression::Copy(e) => self.generate_copy(e),
3562            Expression::CopyParameter(e) => self.generate_copy_parameter(e),
3563            Expression::Corr(e) => self.generate_corr(e),
3564            Expression::CosineDistance(e) => self.generate_cosine_distance(e),
3565            Expression::CovarPop(e) => self.generate_covar_pop(e),
3566            Expression::CovarSamp(e) => self.generate_covar_samp(e),
3567            Expression::Credentials(e) => self.generate_credentials(e),
3568            Expression::CredentialsProperty(e) => self.generate_credentials_property(e),
3569            Expression::Cte(e) => self.generate_cte(e),
3570            Expression::Cube(e) => self.generate_cube(e),
3571            Expression::CurrentDatetime(e) => self.generate_current_datetime(e),
3572            Expression::CurrentSchema(e) => self.generate_current_schema(e),
3573            Expression::CurrentSchemas(e) => self.generate_current_schemas(e),
3574            Expression::CurrentUser(e) => self.generate_current_user(e),
3575            Expression::DPipe(e) => self.generate_d_pipe(e),
3576            Expression::DataBlocksizeProperty(e) => self.generate_data_blocksize_property(e),
3577            Expression::DataDeletionProperty(e) => self.generate_data_deletion_property(e),
3578            Expression::Date(e) => self.generate_date_func(e),
3579            Expression::DateBin(e) => self.generate_date_bin(e),
3580            Expression::DateFormatColumnConstraint(e) => {
3581                self.generate_date_format_column_constraint(e)
3582            }
3583            Expression::DateFromParts(e) => self.generate_date_from_parts(e),
3584            Expression::Datetime(e) => self.generate_datetime(e),
3585            Expression::DatetimeAdd(e) => self.generate_datetime_add(e),
3586            Expression::DatetimeDiff(e) => self.generate_datetime_diff(e),
3587            Expression::DatetimeSub(e) => self.generate_datetime_sub(e),
3588            Expression::DatetimeTrunc(e) => self.generate_datetime_trunc(e),
3589            Expression::Dayname(e) => self.generate_dayname(e),
3590            Expression::Declare(e) => self.generate_declare(e),
3591            Expression::DeclareItem(e) => self.generate_declare_item(e),
3592            Expression::DecodeCase(e) => self.generate_decode_case(e),
3593            Expression::DecompressBinary(e) => self.generate_decompress_binary(e),
3594            Expression::DecompressString(e) => self.generate_decompress_string(e),
3595            Expression::Decrypt(e) => self.generate_decrypt(e),
3596            Expression::DecryptRaw(e) => self.generate_decrypt_raw(e),
3597            Expression::DefaultColumnConstraint(e) => {
3598                self.write_keyword("DEFAULT");
3599                self.write_space();
3600                self.generate_expression(&e.this)?;
3601                if let Some(ref col) = e.for_column {
3602                    self.write_space();
3603                    self.write_keyword("FOR");
3604                    self.write_space();
3605                    self.generate_identifier(col)?;
3606                }
3607                Ok(())
3608            }
3609            Expression::DefinerProperty(e) => self.generate_definer_property(e),
3610            Expression::Detach(e) => self.generate_detach(e),
3611            Expression::DictProperty(e) => self.generate_dict_property(e),
3612            Expression::DictRange(e) => self.generate_dict_range(e),
3613            Expression::Directory(e) => self.generate_directory(e),
3614            Expression::DistKeyProperty(e) => self.generate_dist_key_property(e),
3615            Expression::DistStyleProperty(e) => self.generate_dist_style_property(e),
3616            Expression::DistributeBy(e) => self.generate_distribute_by(e),
3617            Expression::DistributedByProperty(e) => self.generate_distributed_by_property(e),
3618            Expression::DotProduct(e) => self.generate_dot_product(e),
3619            Expression::DropPartition(e) => self.generate_drop_partition(e),
3620            Expression::DuplicateKeyProperty(e) => self.generate_duplicate_key_property(e),
3621            Expression::Elt(e) => self.generate_elt(e),
3622            Expression::Encode(e) => self.generate_encode(e),
3623            Expression::EncodeProperty(e) => self.generate_encode_property(e),
3624            Expression::Encrypt(e) => self.generate_encrypt(e),
3625            Expression::EncryptRaw(e) => self.generate_encrypt_raw(e),
3626            Expression::EngineProperty(e) => self.generate_engine_property(e),
3627            Expression::EnviromentProperty(e) => self.generate_enviroment_property(e),
3628            Expression::EphemeralColumnConstraint(e) => {
3629                self.generate_ephemeral_column_constraint(e)
3630            }
3631            Expression::EqualNull(e) => self.generate_equal_null(e),
3632            Expression::EuclideanDistance(e) => self.generate_euclidean_distance(e),
3633            Expression::ExecuteAsProperty(e) => self.generate_execute_as_property(e),
3634            Expression::Export(e) => self.generate_export(e),
3635            Expression::ExternalProperty(e) => self.generate_external_property(e),
3636            Expression::FallbackProperty(e) => self.generate_fallback_property(e),
3637            Expression::FarmFingerprint(e) => self.generate_farm_fingerprint(e),
3638            Expression::FeaturesAtTime(e) => self.generate_features_at_time(e),
3639            Expression::Fetch(e) => self.generate_fetch(e),
3640            Expression::FileFormatProperty(e) => self.generate_file_format_property(e),
3641            Expression::Filter(e) => self.generate_filter(e),
3642            Expression::Float64(e) => self.generate_float64(e),
3643            Expression::ForIn(e) => self.generate_for_in(e),
3644            Expression::ForeignKey(e) => self.generate_foreign_key(e),
3645            Expression::Format(e) => self.generate_format(e),
3646            Expression::FormatPhrase(e) => self.generate_format_phrase(e),
3647            Expression::FreespaceProperty(e) => self.generate_freespace_property(e),
3648            Expression::From(e) => self.generate_from(e),
3649            Expression::FromBase(e) => self.generate_from_base(e),
3650            Expression::FromTimeZone(e) => self.generate_from_time_zone(e),
3651            Expression::GapFill(e) => self.generate_gap_fill(e),
3652            Expression::GenerateDateArray(e) => self.generate_generate_date_array(e),
3653            Expression::GenerateEmbedding(e) => self.generate_generate_embedding(e),
3654            Expression::GenerateSeries(e) => self.generate_generate_series(e),
3655            Expression::GenerateTimestampArray(e) => self.generate_generate_timestamp_array(e),
3656            Expression::GeneratedAsIdentityColumnConstraint(e) => {
3657                self.generate_generated_as_identity_column_constraint(e)
3658            }
3659            Expression::GeneratedAsRowColumnConstraint(e) => {
3660                self.generate_generated_as_row_column_constraint(e)
3661            }
3662            Expression::Get(e) => self.generate_get(e),
3663            Expression::GetExtract(e) => self.generate_get_extract(e),
3664            Expression::Getbit(e) => self.generate_getbit(e),
3665            Expression::GrantPrincipal(e) => self.generate_grant_principal(e),
3666            Expression::GrantPrivilege(e) => self.generate_grant_privilege(e),
3667            Expression::Group(e) => self.generate_group(e),
3668            Expression::GroupBy(e) => self.generate_group_by(e),
3669            Expression::Grouping(e) => self.generate_grouping(e),
3670            Expression::GroupingId(e) => self.generate_grouping_id(e),
3671            Expression::GroupingSets(e) => self.generate_grouping_sets(e),
3672            Expression::HashAgg(e) => self.generate_hash_agg(e),
3673            Expression::Having(e) => self.generate_having(e),
3674            Expression::HavingMax(e) => self.generate_having_max(e),
3675            Expression::Heredoc(e) => self.generate_heredoc(e),
3676            Expression::HexEncode(e) => self.generate_hex_encode(e),
3677            Expression::Hll(e) => self.generate_hll(e),
3678            Expression::InOutColumnConstraint(e) => self.generate_in_out_column_constraint(e),
3679            Expression::IncludeProperty(e) => self.generate_include_property(e),
3680            Expression::Index(e) => self.generate_index(e),
3681            Expression::IndexColumnConstraint(e) => self.generate_index_column_constraint(e),
3682            Expression::IndexConstraintOption(e) => self.generate_index_constraint_option(e),
3683            Expression::IndexParameters(e) => self.generate_index_parameters(e),
3684            Expression::IndexTableHint(e) => self.generate_index_table_hint(e),
3685            Expression::InheritsProperty(e) => self.generate_inherits_property(e),
3686            Expression::InputModelProperty(e) => self.generate_input_model_property(e),
3687            Expression::InputOutputFormat(e) => self.generate_input_output_format(e),
3688            Expression::Install(e) => self.generate_install(e),
3689            Expression::IntervalOp(e) => self.generate_interval_op(e),
3690            Expression::IntervalSpan(e) => self.generate_interval_span(e),
3691            Expression::IntoClause(e) => self.generate_into_clause(e),
3692            Expression::Introducer(e) => self.generate_introducer(e),
3693            Expression::IsolatedLoadingProperty(e) => self.generate_isolated_loading_property(e),
3694            Expression::JSON(e) => self.generate_json(e),
3695            Expression::JSONArray(e) => self.generate_json_array(e),
3696            Expression::JSONArrayAgg(e) => self.generate_json_array_agg_struct(e),
3697            Expression::JSONArrayAppend(e) => self.generate_json_array_append(e),
3698            Expression::JSONArrayContains(e) => self.generate_json_array_contains(e),
3699            Expression::JSONArrayInsert(e) => self.generate_json_array_insert(e),
3700            Expression::JSONBExists(e) => self.generate_jsonb_exists(e),
3701            Expression::JSONBExtractScalar(e) => self.generate_jsonb_extract_scalar(e),
3702            Expression::JSONBObjectAgg(e) => self.generate_jsonb_object_agg(e),
3703            Expression::JSONObjectAgg(e) => self.generate_json_object_agg_struct(e),
3704            Expression::JSONColumnDef(e) => self.generate_json_column_def(e),
3705            Expression::JSONExists(e) => self.generate_json_exists(e),
3706            Expression::JSONCast(e) => self.generate_json_cast(e),
3707            Expression::JSONExtract(e) => self.generate_json_extract_path(e),
3708            Expression::JSONExtractArray(e) => self.generate_json_extract_array(e),
3709            Expression::JSONExtractQuote(e) => self.generate_json_extract_quote(e),
3710            Expression::JSONExtractScalar(e) => self.generate_json_extract_scalar(e),
3711            Expression::JSONFormat(e) => self.generate_json_format(e),
3712            Expression::JSONKeyValue(e) => self.generate_json_key_value(e),
3713            Expression::JSONKeys(e) => self.generate_json_keys(e),
3714            Expression::JSONKeysAtDepth(e) => self.generate_json_keys_at_depth(e),
3715            Expression::JSONPath(e) => self.generate_json_path_expr(e),
3716            Expression::JSONPathFilter(e) => self.generate_json_path_filter(e),
3717            Expression::JSONPathKey(e) => self.generate_json_path_key(e),
3718            Expression::JSONPathRecursive(e) => self.generate_json_path_recursive(e),
3719            Expression::JSONPathRoot(_) => self.generate_json_path_root(),
3720            Expression::JSONPathScript(e) => self.generate_json_path_script(e),
3721            Expression::JSONPathSelector(e) => self.generate_json_path_selector(e),
3722            Expression::JSONPathSlice(e) => self.generate_json_path_slice(e),
3723            Expression::JSONPathSubscript(e) => self.generate_json_path_subscript(e),
3724            Expression::JSONPathUnion(e) => self.generate_json_path_union(e),
3725            Expression::JSONRemove(e) => self.generate_json_remove(e),
3726            Expression::JSONSchema(e) => self.generate_json_schema(e),
3727            Expression::JSONSet(e) => self.generate_json_set(e),
3728            Expression::JSONStripNulls(e) => self.generate_json_strip_nulls(e),
3729            Expression::JSONTable(e) => self.generate_json_table(e),
3730            Expression::JSONType(e) => self.generate_json_type(e),
3731            Expression::JSONValue(e) => self.generate_json_value(e),
3732            Expression::JSONValueArray(e) => self.generate_json_value_array(e),
3733            Expression::JarowinklerSimilarity(e) => self.generate_jarowinkler_similarity(e),
3734            Expression::JoinHint(e) => self.generate_join_hint(e),
3735            Expression::JournalProperty(e) => self.generate_journal_property(e),
3736            Expression::LanguageProperty(e) => self.generate_language_property(e),
3737            Expression::Lateral(e) => self.generate_lateral(e),
3738            Expression::LikeProperty(e) => self.generate_like_property(e),
3739            Expression::Limit(e) => self.generate_limit(e),
3740            Expression::LimitOptions(e) => self.generate_limit_options(e),
3741            Expression::List(e) => self.generate_list(e),
3742            Expression::ToMap(e) => self.generate_tomap(e),
3743            Expression::Localtime(e) => self.generate_localtime(e),
3744            Expression::Localtimestamp(e) => self.generate_localtimestamp(e),
3745            Expression::LocationProperty(e) => self.generate_location_property(e),
3746            Expression::Lock(e) => self.generate_lock(e),
3747            Expression::LockProperty(e) => self.generate_lock_property(e),
3748            Expression::LockingProperty(e) => self.generate_locking_property(e),
3749            Expression::LockingStatement(e) => self.generate_locking_statement(e),
3750            Expression::LogProperty(e) => self.generate_log_property(e),
3751            Expression::MD5Digest(e) => self.generate_md5_digest(e),
3752            Expression::MLForecast(e) => self.generate_ml_forecast(e),
3753            Expression::MLTranslate(e) => self.generate_ml_translate(e),
3754            Expression::MakeInterval(e) => self.generate_make_interval(e),
3755            Expression::ManhattanDistance(e) => self.generate_manhattan_distance(e),
3756            Expression::Map(e) => self.generate_map(e),
3757            Expression::MapCat(e) => self.generate_map_cat(e),
3758            Expression::MapDelete(e) => self.generate_map_delete(e),
3759            Expression::MapInsert(e) => self.generate_map_insert(e),
3760            Expression::MapPick(e) => self.generate_map_pick(e),
3761            Expression::MaskingPolicyColumnConstraint(e) => {
3762                self.generate_masking_policy_column_constraint(e)
3763            }
3764            Expression::MatchAgainst(e) => self.generate_match_against(e),
3765            Expression::MatchRecognizeMeasure(e) => self.generate_match_recognize_measure(e),
3766            Expression::MaterializedProperty(e) => self.generate_materialized_property(e),
3767            Expression::Merge(e) => self.generate_merge(e),
3768            Expression::MergeBlockRatioProperty(e) => self.generate_merge_block_ratio_property(e),
3769            Expression::MergeTreeTTL(e) => self.generate_merge_tree_ttl(e),
3770            Expression::MergeTreeTTLAction(e) => self.generate_merge_tree_ttl_action(e),
3771            Expression::Minhash(e) => self.generate_minhash(e),
3772            Expression::ModelAttribute(e) => self.generate_model_attribute(e),
3773            Expression::Monthname(e) => self.generate_monthname(e),
3774            Expression::MultitableInserts(e) => self.generate_multitable_inserts(e),
3775            Expression::NextValueFor(e) => self.generate_next_value_for(e),
3776            Expression::Normal(e) => self.generate_normal(e),
3777            Expression::Normalize(e) => self.generate_normalize(e),
3778            Expression::NotNullColumnConstraint(e) => self.generate_not_null_column_constraint(e),
3779            Expression::Nullif(e) => self.generate_nullif(e),
3780            Expression::NumberToStr(e) => self.generate_number_to_str(e),
3781            Expression::ObjectAgg(e) => self.generate_object_agg(e),
3782            Expression::ObjectIdentifier(e) => self.generate_object_identifier(e),
3783            Expression::ObjectInsert(e) => self.generate_object_insert(e),
3784            Expression::Offset(e) => self.generate_offset(e),
3785            Expression::Qualify(e) => self.generate_qualify(e),
3786            Expression::OnCluster(e) => self.generate_on_cluster(e),
3787            Expression::OnCommitProperty(e) => self.generate_on_commit_property(e),
3788            Expression::OnCondition(e) => self.generate_on_condition(e),
3789            Expression::OnConflict(e) => self.generate_on_conflict(e),
3790            Expression::OnProperty(e) => self.generate_on_property(e),
3791            Expression::Opclass(e) => self.generate_opclass(e),
3792            Expression::OpenJSON(e) => self.generate_open_json(e),
3793            Expression::OpenJSONColumnDef(e) => self.generate_open_json_column_def(e),
3794            Expression::Operator(e) => self.generate_operator(e),
3795            Expression::OrderBy(e) => self.generate_order_by(e),
3796            Expression::OutputModelProperty(e) => self.generate_output_model_property(e),
3797            Expression::OverflowTruncateBehavior(e) => self.generate_overflow_truncate_behavior(e),
3798            Expression::ParameterizedAgg(e) => self.generate_parameterized_agg(e),
3799            Expression::ParseDatetime(e) => self.generate_parse_datetime(e),
3800            Expression::ParseIp(e) => self.generate_parse_ip(e),
3801            Expression::ParseJSON(e) => self.generate_parse_json(e),
3802            Expression::ParseTime(e) => self.generate_parse_time(e),
3803            Expression::ParseUrl(e) => self.generate_parse_url(e),
3804            Expression::Partition(e) => self.generate_partition_expr(e),
3805            Expression::PartitionBoundSpec(e) => self.generate_partition_bound_spec(e),
3806            Expression::PartitionByListProperty(e) => self.generate_partition_by_list_property(e),
3807            Expression::PartitionByRangeProperty(e) => self.generate_partition_by_range_property(e),
3808            Expression::PartitionByRangePropertyDynamic(e) => {
3809                self.generate_partition_by_range_property_dynamic(e)
3810            }
3811            Expression::PartitionByTruncate(e) => self.generate_partition_by_truncate(e),
3812            Expression::PartitionList(e) => self.generate_partition_list(e),
3813            Expression::PartitionRange(e) => self.generate_partition_range(e),
3814            Expression::PartitionByProperty(e) => self.generate_partition_by_property(e),
3815            Expression::PartitionedByBucket(e) => self.generate_partitioned_by_bucket(e),
3816            Expression::PartitionedByProperty(e) => self.generate_partitioned_by_property(e),
3817            Expression::PartitionedOfProperty(e) => self.generate_partitioned_of_property(e),
3818            Expression::PeriodForSystemTimeConstraint(e) => {
3819                self.generate_period_for_system_time_constraint(e)
3820            }
3821            Expression::PivotAlias(e) => self.generate_pivot_alias(e),
3822            Expression::PivotAny(e) => self.generate_pivot_any(e),
3823            Expression::Predict(e) => self.generate_predict(e),
3824            Expression::PreviousDay(e) => self.generate_previous_day(e),
3825            Expression::PrimaryKey(e) => self.generate_primary_key(e),
3826            Expression::PrimaryKeyColumnConstraint(e) => {
3827                self.generate_primary_key_column_constraint(e)
3828            }
3829            Expression::PathColumnConstraint(e) => self.generate_path_column_constraint(e),
3830            Expression::ProjectionDef(e) => self.generate_projection_def(e),
3831            Expression::OptionsProperty(e) => self.generate_options_property(e),
3832            Expression::Properties(e) => self.generate_properties(e),
3833            Expression::Property(e) => self.generate_property(e),
3834            Expression::PseudoType(e) => self.generate_pseudo_type(e),
3835            Expression::Put(e) => self.generate_put(e),
3836            Expression::Quantile(e) => self.generate_quantile(e),
3837            Expression::QueryBand(e) => self.generate_query_band(e),
3838            Expression::QueryOption(e) => self.generate_query_option(e),
3839            Expression::QueryTransform(e) => self.generate_query_transform(e),
3840            Expression::Randn(e) => self.generate_randn(e),
3841            Expression::Randstr(e) => self.generate_randstr(e),
3842            Expression::RangeBucket(e) => self.generate_range_bucket(e),
3843            Expression::RangeN(e) => self.generate_range_n(e),
3844            Expression::ReadCSV(e) => self.generate_read_csv(e),
3845            Expression::ReadParquet(e) => self.generate_read_parquet(e),
3846            Expression::RecursiveWithSearch(e) => self.generate_recursive_with_search(e),
3847            Expression::Reduce(e) => self.generate_reduce(e),
3848            Expression::Reference(e) => self.generate_reference(e),
3849            Expression::Refresh(e) => self.generate_refresh(e),
3850            Expression::RefreshTriggerProperty(e) => self.generate_refresh_trigger_property(e),
3851            Expression::RegexpCount(e) => self.generate_regexp_count(e),
3852            Expression::RegexpExtractAll(e) => self.generate_regexp_extract_all(e),
3853            Expression::RegexpFullMatch(e) => self.generate_regexp_full_match(e),
3854            Expression::RegexpILike(e) => self.generate_regexp_i_like(e),
3855            Expression::RegexpInstr(e) => self.generate_regexp_instr(e),
3856            Expression::RegexpSplit(e) => self.generate_regexp_split(e),
3857            Expression::RegrAvgx(e) => self.generate_regr_avgx(e),
3858            Expression::RegrAvgy(e) => self.generate_regr_avgy(e),
3859            Expression::RegrCount(e) => self.generate_regr_count(e),
3860            Expression::RegrIntercept(e) => self.generate_regr_intercept(e),
3861            Expression::RegrR2(e) => self.generate_regr_r2(e),
3862            Expression::RegrSlope(e) => self.generate_regr_slope(e),
3863            Expression::RegrSxx(e) => self.generate_regr_sxx(e),
3864            Expression::RegrSxy(e) => self.generate_regr_sxy(e),
3865            Expression::RegrSyy(e) => self.generate_regr_syy(e),
3866            Expression::RegrValx(e) => self.generate_regr_valx(e),
3867            Expression::RegrValy(e) => self.generate_regr_valy(e),
3868            Expression::RemoteWithConnectionModelProperty(e) => {
3869                self.generate_remote_with_connection_model_property(e)
3870            }
3871            Expression::RenameColumn(e) => self.generate_rename_column(e),
3872            Expression::ReplacePartition(e) => self.generate_replace_partition(e),
3873            Expression::Returning(e) => self.generate_returning(e),
3874            Expression::ReturnsProperty(e) => self.generate_returns_property(e),
3875            Expression::Rollback(e) => self.generate_rollback(e),
3876            Expression::Rollup(e) => self.generate_rollup(e),
3877            Expression::RowFormatDelimitedProperty(e) => {
3878                self.generate_row_format_delimited_property(e)
3879            }
3880            Expression::RowFormatProperty(e) => self.generate_row_format_property(e),
3881            Expression::RowFormatSerdeProperty(e) => self.generate_row_format_serde_property(e),
3882            Expression::SHA2(e) => self.generate_sha2(e),
3883            Expression::SHA2Digest(e) => self.generate_sha2_digest(e),
3884            Expression::SafeAdd(e) => self.generate_safe_add(e),
3885            Expression::SafeDivide(e) => self.generate_safe_divide(e),
3886            Expression::SafeMultiply(e) => self.generate_safe_multiply(e),
3887            Expression::SafeSubtract(e) => self.generate_safe_subtract(e),
3888            Expression::SampleProperty(e) => self.generate_sample_property(e),
3889            Expression::Schema(e) => self.generate_schema(e),
3890            Expression::SchemaCommentProperty(e) => self.generate_schema_comment_property(e),
3891            Expression::ScopeResolution(e) => self.generate_scope_resolution(e),
3892            Expression::Search(e) => self.generate_search(e),
3893            Expression::SearchIp(e) => self.generate_search_ip(e),
3894            Expression::SecurityProperty(e) => self.generate_security_property(e),
3895            Expression::SemanticView(e) => self.generate_semantic_view(e),
3896            Expression::SequenceProperties(e) => self.generate_sequence_properties(e),
3897            Expression::SerdeProperties(e) => self.generate_serde_properties(e),
3898            Expression::SessionParameter(e) => self.generate_session_parameter(e),
3899            Expression::Set(e) => self.generate_set(e),
3900            Expression::SetConfigProperty(e) => self.generate_set_config_property(e),
3901            Expression::SetItem(e) => self.generate_set_item(e),
3902            Expression::SetOperation(e) => self.generate_set_operation(e),
3903            Expression::SetProperty(e) => self.generate_set_property(e),
3904            Expression::SettingsProperty(e) => self.generate_settings_property(e),
3905            Expression::SharingProperty(e) => self.generate_sharing_property(e),
3906            Expression::Slice(e) => self.generate_slice(e),
3907            Expression::SortArray(e) => self.generate_sort_array(e),
3908            Expression::SortBy(e) => self.generate_sort_by(e),
3909            Expression::SortKeyProperty(e) => self.generate_sort_key_property(e),
3910            Expression::SplitPart(e) => self.generate_split_part(e),
3911            Expression::SqlReadWriteProperty(e) => self.generate_sql_read_write_property(e),
3912            Expression::SqlSecurityProperty(e) => self.generate_sql_security_property(e),
3913            Expression::StDistance(e) => self.generate_st_distance(e),
3914            Expression::StPoint(e) => self.generate_st_point(e),
3915            Expression::StabilityProperty(e) => self.generate_stability_property(e),
3916            Expression::StandardHash(e) => self.generate_standard_hash(e),
3917            Expression::StorageHandlerProperty(e) => self.generate_storage_handler_property(e),
3918            Expression::StrPosition(e) => self.generate_str_position(e),
3919            Expression::StrToDate(e) => self.generate_str_to_date(e),
3920            Expression::DateStrToDate(f) => self.generate_simple_func("DATE_STR_TO_DATE", &f.this),
3921            Expression::DateToDateStr(f) => self.generate_simple_func("DATE_TO_DATE_STR", &f.this),
3922            Expression::StrToMap(e) => self.generate_str_to_map(e),
3923            Expression::StrToTime(e) => self.generate_str_to_time(e),
3924            Expression::StrToUnix(e) => self.generate_str_to_unix(e),
3925            Expression::StringToArray(e) => self.generate_string_to_array(e),
3926            Expression::Struct(e) => self.generate_struct(e),
3927            Expression::Stuff(e) => self.generate_stuff(e),
3928            Expression::SubstringIndex(e) => self.generate_substring_index(e),
3929            Expression::Summarize(e) => self.generate_summarize(e),
3930            Expression::Systimestamp(e) => self.generate_systimestamp(e),
3931            Expression::TableAlias(e) => self.generate_table_alias(e),
3932            Expression::TableFromRows(e) => self.generate_table_from_rows(e),
3933            Expression::RowsFrom(e) => self.generate_rows_from(e),
3934            Expression::TableSample(e) => self.generate_table_sample(e),
3935            Expression::Tag(e) => self.generate_tag(e),
3936            Expression::Tags(e) => self.generate_tags(e),
3937            Expression::TemporaryProperty(e) => self.generate_temporary_property(e),
3938            Expression::Time(e) => self.generate_time_func(e),
3939            Expression::TimeAdd(e) => self.generate_time_add(e),
3940            Expression::TimeDiff(e) => self.generate_time_diff(e),
3941            Expression::TimeFromParts(e) => self.generate_time_from_parts(e),
3942            Expression::TimeSlice(e) => self.generate_time_slice(e),
3943            Expression::TimeStrToDate(e) => self.generate_time_str_to_date(e),
3944            Expression::TimeStrToTime(e) => self.generate_time_str_to_time(e),
3945            Expression::TimeSub(e) => self.generate_time_sub(e),
3946            Expression::TimeToStr(e) => self.generate_time_to_str(e),
3947            Expression::TimeToUnix(e) => self.generate_time_to_unix(e),
3948            Expression::TimeTrunc(e) => self.generate_time_trunc(e),
3949            Expression::TimeUnit(e) => self.generate_time_unit(e),
3950            Expression::Timestamp(e) => self.generate_timestamp_func(e),
3951            Expression::TimestampAdd(e) => self.generate_timestamp_add(e),
3952            Expression::TimestampDiff(e) => self.generate_timestamp_diff(e),
3953            Expression::TimestampFromParts(e) => self.generate_timestamp_from_parts(e),
3954            Expression::TimestampSub(e) => self.generate_timestamp_sub(e),
3955            Expression::TimestampTzFromParts(e) => self.generate_timestamp_tz_from_parts(e),
3956            Expression::ToBinary(e) => self.generate_to_binary(e),
3957            Expression::ToBoolean(e) => self.generate_to_boolean(e),
3958            Expression::ToChar(e) => self.generate_to_char(e),
3959            Expression::ToDecfloat(e) => self.generate_to_decfloat(e),
3960            Expression::ToDouble(e) => self.generate_to_double(e),
3961            Expression::ToFile(e) => self.generate_to_file(e),
3962            Expression::ToNumber(e) => self.generate_to_number(e),
3963            Expression::ToTableProperty(e) => self.generate_to_table_property(e),
3964            Expression::Transaction(e) => self.generate_transaction(e),
3965            Expression::Transform(e) => self.generate_transform(e),
3966            Expression::TransformModelProperty(e) => self.generate_transform_model_property(e),
3967            Expression::TransientProperty(e) => self.generate_transient_property(e),
3968            Expression::Translate(e) => self.generate_translate(e),
3969            Expression::TranslateCharacters(e) => self.generate_translate_characters(e),
3970            Expression::TruncateTable(e) => self.generate_truncate_table(e),
3971            Expression::TryBase64DecodeBinary(e) => self.generate_try_base64_decode_binary(e),
3972            Expression::TryBase64DecodeString(e) => self.generate_try_base64_decode_string(e),
3973            Expression::TryToDecfloat(e) => self.generate_try_to_decfloat(e),
3974            Expression::TsOrDsAdd(e) => self.generate_ts_or_ds_add(e),
3975            Expression::TsOrDsDiff(e) => self.generate_ts_or_ds_diff(e),
3976            Expression::TsOrDsToDate(e) => self.generate_ts_or_ds_to_date(e),
3977            Expression::TsOrDsToTime(e) => self.generate_ts_or_ds_to_time(e),
3978            Expression::Unhex(e) => self.generate_unhex(e),
3979            Expression::UnicodeString(e) => self.generate_unicode_string(e),
3980            Expression::Uniform(e) => self.generate_uniform(e),
3981            Expression::UniqueColumnConstraint(e) => self.generate_unique_column_constraint(e),
3982            Expression::UniqueKeyProperty(e) => self.generate_unique_key_property(e),
3983            Expression::RollupProperty(e) => self.generate_rollup_property(e),
3984            Expression::UnixToStr(e) => self.generate_unix_to_str(e),
3985            Expression::UnixToTime(e) => self.generate_unix_to_time(e),
3986            Expression::UnpivotColumns(e) => self.generate_unpivot_columns(e),
3987            Expression::UserDefinedFunction(e) => self.generate_user_defined_function(e),
3988            Expression::UsingTemplateProperty(e) => self.generate_using_template_property(e),
3989            Expression::UtcTime(e) => self.generate_utc_time(e),
3990            Expression::UtcTimestamp(e) => self.generate_utc_timestamp(e),
3991            Expression::Uuid(e) => self.generate_uuid(e),
3992            Expression::Var(v) => {
3993                if matches!(self.config.dialect, Some(DialectType::MySQL))
3994                    && v.this.len() > 2
3995                    && (v.this.starts_with("0x") || v.this.starts_with("0X"))
3996                    && !v.this[2..].chars().all(|c| c.is_ascii_hexdigit())
3997                {
3998                    return self.generate_identifier(&Identifier {
3999                        name: v.this.clone(),
4000                        quoted: true,
4001                        trailing_comments: Vec::new(),
4002                        span: None,
4003                    });
4004                }
4005                self.write(&v.this);
4006                Ok(())
4007            }
4008            Expression::Variadic(e) => {
4009                self.write_keyword("VARIADIC");
4010                self.write_space();
4011                self.generate_expression(&e.this)?;
4012                Ok(())
4013            }
4014            Expression::VarMap(e) => self.generate_var_map(e),
4015            Expression::VectorSearch(e) => self.generate_vector_search(e),
4016            Expression::Version(e) => self.generate_version(e),
4017            Expression::ViewAttributeProperty(e) => self.generate_view_attribute_property(e),
4018            Expression::VolatileProperty(e) => self.generate_volatile_property(e),
4019            Expression::WatermarkColumnConstraint(e) => {
4020                self.generate_watermark_column_constraint(e)
4021            }
4022            Expression::Week(e) => self.generate_week(e),
4023            Expression::When(e) => self.generate_when(e),
4024            Expression::Whens(e) => self.generate_whens(e),
4025            Expression::Where(e) => self.generate_where(e),
4026            Expression::WidthBucket(e) => self.generate_width_bucket(e),
4027            Expression::Window(e) => self.generate_window(e),
4028            Expression::WindowSpec(e) => self.generate_window_spec(e),
4029            Expression::WithDataProperty(e) => self.generate_with_data_property(e),
4030            Expression::WithFill(e) => self.generate_with_fill(e),
4031            Expression::WithJournalTableProperty(e) => self.generate_with_journal_table_property(e),
4032            Expression::WithOperator(e) => self.generate_with_operator(e),
4033            Expression::WithProcedureOptions(e) => self.generate_with_procedure_options(e),
4034            Expression::WithSchemaBindingProperty(e) => {
4035                self.generate_with_schema_binding_property(e)
4036            }
4037            Expression::WithSystemVersioningProperty(e) => {
4038                self.generate_with_system_versioning_property(e)
4039            }
4040            Expression::WithTableHint(e) => self.generate_with_table_hint(e),
4041            Expression::XMLElement(e) => self.generate_xml_element(e),
4042            Expression::XMLGet(e) => self.generate_xml_get(e),
4043            Expression::XMLKeyValueOption(e) => self.generate_xml_key_value_option(e),
4044            Expression::XMLTable(e) => self.generate_xml_table(e),
4045            Expression::Xor(e) => self.generate_xor(e),
4046            Expression::Zipf(e) => self.generate_zipf(e),
4047            _ => self.write_unsupported_comment("unsupported expression"),
4048        }
4049    }
4050
4051    fn generate_select(&mut self, select: &Select) -> Result<()> {
4052        use crate::dialects::DialectType;
4053
4054        // Redshift-style EXCLUDE: for dialects other than Redshift, wrap in a derived table
4055        // e.g., SELECT *, col4 EXCLUDE (col2, col3) FROM t
4056        //   → SELECT * EXCLUDE (col2, col3) FROM (SELECT *, col4 FROM t)
4057        if let Some(exclude) = &select.exclude {
4058            if !exclude.is_empty() && !matches!(self.config.dialect, Some(DialectType::Redshift)) {
4059                // Build the inner select (same as original but without exclude)
4060                let mut inner_select = select.clone();
4061                inner_select.exclude = None;
4062                let inner_expr = Expression::Select(Box::new(inner_select));
4063
4064                // Build the subquery
4065                let subquery = crate::expressions::Subquery {
4066                    this: inner_expr,
4067                    alias: None,
4068                    column_aliases: Vec::new(),
4069                    alias_explicit_as: false,
4070                    alias_keyword: None,
4071                    order_by: None,
4072                    limit: None,
4073                    offset: None,
4074                    distribute_by: None,
4075                    sort_by: None,
4076                    cluster_by: None,
4077                    lateral: false,
4078                    modifiers_inside: false,
4079                    trailing_comments: Vec::new(),
4080                    inferred_type: None,
4081                };
4082
4083                // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner)
4084                let star = Expression::Star(crate::expressions::Star {
4085                    table: None,
4086                    except: Some(
4087                        exclude
4088                            .iter()
4089                            .map(|e| match e {
4090                                Expression::Column(col) => col.name.clone(),
4091                                Expression::Identifier(id) => id.clone(),
4092                                _ => crate::expressions::Identifier::new("unknown".to_string()),
4093                            })
4094                            .collect(),
4095                    ),
4096                    replace: None,
4097                    rename: None,
4098                    trailing_comments: Vec::new(),
4099                    span: None,
4100                });
4101
4102                let outer_select = Select {
4103                    expressions: vec![star],
4104                    from: Some(crate::expressions::From {
4105                        expressions: vec![Expression::Subquery(Box::new(subquery))],
4106                    }),
4107                    ..Select::new()
4108                };
4109
4110                return self.generate_select(&outer_select);
4111            }
4112        }
4113
4114        // Output leading comments before SELECT
4115        for comment in &select.leading_comments {
4116            self.write_formatted_comment(comment);
4117            self.write(" ");
4118        }
4119
4120        // WITH clause
4121        if let Some(with) = &select.with {
4122            self.generate_with(with)?;
4123            if self.config.pretty {
4124                self.write_newline();
4125                self.write_indent();
4126            } else {
4127                self.write_space();
4128            }
4129        }
4130
4131        // Output post-SELECT comments (comments that appeared after SELECT keyword)
4132        // These are output BEFORE SELECT, as Python SQLGlot normalizes them this way
4133        for comment in &select.post_select_comments {
4134            self.write_formatted_comment(comment);
4135            self.write(" ");
4136        }
4137
4138        self.write_keyword("SELECT");
4139
4140        // Generate query hint if present /*+ ... */
4141        if let Some(hint) = &select.hint {
4142            self.generate_hint(hint)?;
4143        }
4144
4145        // For SQL Server, convert LIMIT to TOP (structural transformation)
4146        // But only when there's no OFFSET (otherwise use OFFSET/FETCH syntax)
4147        // TOP clause (SQL Server style - before DISTINCT)
4148        let use_top_from_limit = matches!(
4149            self.config.dialect,
4150            Some(DialectType::TSQL) | Some(DialectType::Fabric)
4151        ) && select.top.is_none()
4152            && select.limit.is_some()
4153            && select.offset.is_none(); // Don't use TOP when there's OFFSET
4154
4155        // For TOP-supporting dialects: DISTINCT before TOP
4156        // For non-TOP dialects: TOP is converted to LIMIT later; DISTINCT goes here
4157        let is_top_dialect = matches!(
4158            self.config.dialect,
4159            Some(DialectType::TSQL) | Some(DialectType::Teradata) | Some(DialectType::Fabric)
4160        );
4161        let keep_top_verbatim = !is_top_dialect
4162            && select.limit.is_none()
4163            && select
4164                .top
4165                .as_ref()
4166                .map_or(false, |top| top.percent || top.with_ties);
4167
4168        if select.distinct && (is_top_dialect || select.top.is_some()) {
4169            self.write_space();
4170            self.write_keyword("DISTINCT");
4171        }
4172
4173        if is_top_dialect || keep_top_verbatim {
4174            if let Some(top) = &select.top {
4175                self.write_space();
4176                self.write_keyword("TOP");
4177                if top.parenthesized {
4178                    if matches!(&top.this, Expression::Subquery(_) | Expression::Paren(_)) {
4179                        self.write_space();
4180                        self.generate_expression(&top.this)?;
4181                    } else {
4182                        self.write(" (");
4183                        self.generate_expression(&top.this)?;
4184                        self.write(")");
4185                    }
4186                } else {
4187                    self.write_space();
4188                    self.generate_expression(&top.this)?;
4189                }
4190                if top.percent {
4191                    self.write_space();
4192                    self.write_keyword("PERCENT");
4193                }
4194                if top.with_ties {
4195                    self.write_space();
4196                    self.write_keyword("WITH TIES");
4197                }
4198            } else if use_top_from_limit {
4199                // Convert LIMIT to TOP for SQL Server (only when no OFFSET)
4200                if let Some(limit) = &select.limit {
4201                    self.write_space();
4202                    self.write_keyword("TOP");
4203                    // Use parentheses for complex expressions, but not for simple literals
4204                    let is_simple_literal = matches!(&limit.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4205                    if is_simple_literal {
4206                        self.write_space();
4207                        self.generate_expression(&limit.this)?;
4208                    } else {
4209                        self.write(" (");
4210                        self.generate_expression(&limit.this)?;
4211                        self.write(")");
4212                    }
4213                }
4214            }
4215        }
4216
4217        if select.distinct && !is_top_dialect && select.top.is_none() {
4218            self.write_space();
4219            self.write_keyword("DISTINCT");
4220        }
4221
4222        // DISTINCT ON clause (PostgreSQL)
4223        if let Some(distinct_on) = &select.distinct_on {
4224            self.write_space();
4225            self.write_keyword("ON");
4226            self.write(" (");
4227            for (i, expr) in distinct_on.iter().enumerate() {
4228                if i > 0 {
4229                    self.write(", ");
4230                }
4231                self.generate_expression(expr)?;
4232            }
4233            self.write(")");
4234        }
4235
4236        // MySQL operation modifiers (HIGH_PRIORITY, STRAIGHT_JOIN, SQL_CALC_FOUND_ROWS, etc.)
4237        for modifier in &select.operation_modifiers {
4238            self.write_space();
4239            self.write_keyword(modifier);
4240        }
4241
4242        // BigQuery SELECT AS STRUCT / SELECT AS VALUE
4243        if let Some(kind) = &select.kind {
4244            self.write_space();
4245            self.write_keyword("AS");
4246            self.write_space();
4247            self.write_keyword(kind);
4248        }
4249
4250        // Expressions (only if there are any)
4251        if !select.expressions.is_empty() {
4252            if self.config.pretty {
4253                self.write_newline();
4254                self.indent_level += 1;
4255            } else {
4256                self.write_space();
4257            }
4258        }
4259
4260        for (i, expr) in select.expressions.iter().enumerate() {
4261            if i > 0 {
4262                self.write(",");
4263                if self.config.pretty {
4264                    self.write_newline();
4265                } else {
4266                    self.write_space();
4267                }
4268            }
4269            if self.config.pretty {
4270                self.write_indent();
4271            }
4272            self.generate_expression(expr)?;
4273        }
4274
4275        if self.config.pretty && !select.expressions.is_empty() {
4276            self.indent_level -= 1;
4277        }
4278
4279        // Redshift-style EXCLUDE clause at the end of the projection list
4280        // For Redshift dialect: append EXCLUDE (col1, col2) after the expressions
4281        // For other dialects (DuckDB, Snowflake): this is handled by wrapping in a derived table
4282        // (done after the full select is generated below)
4283        if let Some(exclude) = &select.exclude {
4284            if !exclude.is_empty() && matches!(self.config.dialect, Some(DialectType::Redshift)) {
4285                self.write_space();
4286                self.write_keyword("EXCLUDE");
4287                self.write(" (");
4288                for (i, col) in exclude.iter().enumerate() {
4289                    if i > 0 {
4290                        self.write(", ");
4291                    }
4292                    self.generate_expression(col)?;
4293                }
4294                self.write(")");
4295            }
4296        }
4297
4298        // INTO clause (SELECT ... INTO table_name)
4299        // Also handles Oracle PL/SQL: BULK COLLECT INTO v1, v2, ...
4300        if let Some(into) = &select.into {
4301            if self.config.pretty {
4302                self.write_newline();
4303                self.write_indent();
4304            } else {
4305                self.write_space();
4306            }
4307            if into.bulk_collect {
4308                self.write_keyword("BULK COLLECT INTO");
4309            } else {
4310                self.write_keyword("INTO");
4311            }
4312            if into.temporary {
4313                self.write_space();
4314                self.write_keyword("TEMPORARY");
4315            }
4316            if into.unlogged {
4317                self.write_space();
4318                self.write_keyword("UNLOGGED");
4319            }
4320            self.write_space();
4321            // If we have multiple expressions, output them comma-separated
4322            if !into.expressions.is_empty() {
4323                for (i, expr) in into.expressions.iter().enumerate() {
4324                    if i > 0 {
4325                        self.write(", ");
4326                    }
4327                    self.generate_expression(expr)?;
4328                }
4329            } else {
4330                self.generate_expression(&into.this)?;
4331            }
4332        }
4333
4334        // FROM clause
4335        if let Some(from) = &select.from {
4336            if self.config.pretty {
4337                self.write_newline();
4338                self.write_indent();
4339            } else {
4340                self.write_space();
4341            }
4342            self.write_keyword("FROM");
4343            self.write_space();
4344
4345            // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax for multiple tables
4346            // But keep commas when TABLESAMPLE is present (Spark/Hive handle TABLESAMPLE differently with commas)
4347            // Also keep commas when the source dialect is Generic/None and target is one of these dialects
4348            // (Python sqlglot: the Hive/Spark parser marks comma joins as CROSS, but Generic parser keeps them implicit)
4349            let has_tablesample = from
4350                .expressions
4351                .iter()
4352                .any(|e| matches!(e, Expression::TableSample(_)));
4353            let is_cross_join_dialect = matches!(
4354                self.config.dialect,
4355                Some(DialectType::BigQuery)
4356                    | Some(DialectType::Hive)
4357                    | Some(DialectType::Spark)
4358                    | Some(DialectType::Databricks)
4359                    | Some(DialectType::SQLite)
4360                    | Some(DialectType::ClickHouse)
4361            );
4362            // Skip CROSS JOIN conversion when source is Generic/None and target is a CROSS JOIN dialect
4363            // This matches Python sqlglot where comma-to-CROSS-JOIN is done in the dialect's parser, not generator
4364            let source_is_same_as_target = self.config.source_dialect.is_some()
4365                && self.config.source_dialect == self.config.dialect;
4366            let source_is_cross_join_dialect = matches!(
4367                self.config.source_dialect,
4368                Some(DialectType::BigQuery)
4369                    | Some(DialectType::Hive)
4370                    | Some(DialectType::Spark)
4371                    | Some(DialectType::Databricks)
4372                    | Some(DialectType::SQLite)
4373                    | Some(DialectType::ClickHouse)
4374            );
4375            let use_cross_join = !has_tablesample
4376                && is_cross_join_dialect
4377                && (source_is_same_as_target
4378                    || source_is_cross_join_dialect
4379                    || self.config.source_dialect.is_none());
4380
4381            // Snowflake wraps standalone VALUES in FROM clause with parentheses
4382            let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
4383
4384            for (i, expr) in from.expressions.iter().enumerate() {
4385                if i > 0 {
4386                    if use_cross_join {
4387                        self.write(" CROSS JOIN ");
4388                    } else {
4389                        self.write(", ");
4390                    }
4391                }
4392                if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
4393                    self.write("(");
4394                    self.generate_expression(expr)?;
4395                    self.write(")");
4396                } else {
4397                    self.generate_expression(expr)?;
4398                }
4399                // Output leading comments that were on the table name before FROM
4400                // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
4401                let leading = Self::extract_table_leading_comments(expr);
4402                for comment in &leading {
4403                    self.write_space();
4404                    self.write_formatted_comment(comment);
4405                }
4406            }
4407        }
4408
4409        // JOINs - handle nested join structure for pretty printing
4410        // Deferred-condition joins "own" the non-deferred joins that follow them
4411        // until the next deferred join or end of list
4412        if self.config.pretty {
4413            self.generate_joins_with_nesting(&select.joins)?;
4414        } else {
4415            for join in &select.joins {
4416                self.generate_join(join)?;
4417            }
4418            // Output deferred ON/USING conditions (right-to-left, which is reverse order)
4419            for join in select.joins.iter().rev() {
4420                if join.deferred_condition {
4421                    self.generate_join_condition(join)?;
4422                }
4423            }
4424        }
4425
4426        // LATERAL VIEW clauses (Hive/Spark)
4427        for (lv_idx, lateral_view) in select.lateral_views.iter().enumerate() {
4428            self.generate_lateral_view(lateral_view, lv_idx)?;
4429        }
4430
4431        // PREWHERE (ClickHouse)
4432        if let Some(prewhere) = &select.prewhere {
4433            self.write_clause_condition("PREWHERE", prewhere)?;
4434        }
4435
4436        // WHERE
4437        if let Some(where_clause) = &select.where_clause {
4438            self.write_clause_condition("WHERE", &where_clause.this)?;
4439        }
4440
4441        // CONNECT BY (Oracle hierarchical queries)
4442        if let Some(connect) = &select.connect {
4443            self.generate_connect(connect)?;
4444        }
4445
4446        // GROUP BY
4447        if let Some(group_by) = &select.group_by {
4448            if self.config.pretty {
4449                // Output leading comments on their own lines before GROUP BY
4450                for comment in &group_by.comments {
4451                    self.write_newline();
4452                    self.write_indent();
4453                    self.write_formatted_comment(comment);
4454                }
4455                self.write_newline();
4456                self.write_indent();
4457            } else {
4458                self.write_space();
4459                // In non-pretty mode, output comments inline
4460                for comment in &group_by.comments {
4461                    self.write_formatted_comment(comment);
4462                    self.write_space();
4463                }
4464            }
4465            let clickhouse_bare_modifiers =
4466                matches!(self.config.dialect, Some(DialectType::ClickHouse))
4467                    && group_by.all.is_none()
4468                    && (group_by.totals || !group_by.expressions.is_empty())
4469                    && group_by.expressions.iter().all(|expr| match expr {
4470                        Expression::Cube(c) => c.expressions.is_empty(),
4471                        Expression::Rollup(r) => r.expressions.is_empty(),
4472                        _ => false,
4473                    });
4474
4475            if clickhouse_bare_modifiers {
4476                let trailing_cube = group_by
4477                    .expressions
4478                    .iter()
4479                    .any(|expr| matches!(expr, Expression::Cube(c) if c.expressions.is_empty()));
4480                let trailing_rollup = group_by
4481                    .expressions
4482                    .iter()
4483                    .any(|expr| matches!(expr, Expression::Rollup(r) if r.expressions.is_empty()));
4484
4485                if trailing_cube {
4486                    self.write_keyword("WITH CUBE");
4487                } else if trailing_rollup {
4488                    self.write_keyword("WITH ROLLUP");
4489                }
4490
4491                if group_by.totals {
4492                    if trailing_cube || trailing_rollup {
4493                        self.write_space();
4494                    }
4495                    self.write_keyword("WITH TOTALS");
4496                }
4497            } else {
4498                self.write_keyword("GROUP BY");
4499                // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
4500                match group_by.all {
4501                    Some(true) => {
4502                        self.write_space();
4503                        self.write_keyword("ALL");
4504                    }
4505                    Some(false) => {
4506                        self.write_space();
4507                        self.write_keyword("DISTINCT");
4508                    }
4509                    None => {}
4510                }
4511                if !group_by.expressions.is_empty() {
4512                    // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
4513                    // These are represented as Cube/Rollup expressions with empty expressions at the end
4514                    let mut trailing_cube = false;
4515                    let mut trailing_rollup = false;
4516                    let mut plain_expressions: Vec<&Expression> = Vec::new();
4517                    let mut grouping_sets_expressions: Vec<&Expression> = Vec::new();
4518                    let mut cube_expressions: Vec<&Expression> = Vec::new();
4519                    let mut rollup_expressions: Vec<&Expression> = Vec::new();
4520
4521                    for expr in &group_by.expressions {
4522                        match expr {
4523                            Expression::Cube(c) if c.expressions.is_empty() => {
4524                                trailing_cube = true;
4525                            }
4526                            Expression::Rollup(r) if r.expressions.is_empty() => {
4527                                trailing_rollup = true;
4528                            }
4529                            Expression::Function(f) if f.name == "CUBE" => {
4530                                cube_expressions.push(expr);
4531                            }
4532                            Expression::Function(f) if f.name == "ROLLUP" => {
4533                                rollup_expressions.push(expr);
4534                            }
4535                            Expression::Function(f) if f.name == "GROUPING SETS" => {
4536                                grouping_sets_expressions.push(expr);
4537                            }
4538                            _ => {
4539                                plain_expressions.push(expr);
4540                            }
4541                        }
4542                    }
4543
4544                    // Reorder: plain expressions first, then GROUPING SETS, CUBE, ROLLUP
4545                    let mut regular_expressions: Vec<&Expression> = Vec::new();
4546                    regular_expressions.extend(plain_expressions);
4547                    regular_expressions.extend(grouping_sets_expressions);
4548                    regular_expressions.extend(cube_expressions);
4549                    regular_expressions.extend(rollup_expressions);
4550
4551                    if self.config.pretty {
4552                        self.write_newline();
4553                        self.indent_level += 1;
4554                        self.write_indent();
4555                    } else {
4556                        self.write_space();
4557                    }
4558
4559                    for (i, expr) in regular_expressions.iter().enumerate() {
4560                        if i > 0 {
4561                            if self.config.pretty {
4562                                self.write(",");
4563                                self.write_newline();
4564                                self.write_indent();
4565                            } else {
4566                                self.write(", ");
4567                            }
4568                        }
4569                        self.generate_expression(expr)?;
4570                    }
4571
4572                    if self.config.pretty {
4573                        self.indent_level -= 1;
4574                    }
4575
4576                    // Output trailing WITH CUBE or WITH ROLLUP
4577                    if trailing_cube {
4578                        self.write_space();
4579                        self.write_keyword("WITH CUBE");
4580                    } else if trailing_rollup {
4581                        self.write_space();
4582                        self.write_keyword("WITH ROLLUP");
4583                    }
4584                }
4585
4586                // ClickHouse: WITH TOTALS
4587                if group_by.totals {
4588                    self.write_space();
4589                    self.write_keyword("WITH TOTALS");
4590                }
4591            }
4592        }
4593
4594        // HAVING
4595        if let Some(having) = &select.having {
4596            if self.config.pretty {
4597                // Output leading comments on their own lines before HAVING
4598                for comment in &having.comments {
4599                    self.write_newline();
4600                    self.write_indent();
4601                    self.write_formatted_comment(comment);
4602                }
4603            } else {
4604                for comment in &having.comments {
4605                    self.write_space();
4606                    self.write_formatted_comment(comment);
4607                }
4608            }
4609            self.write_clause_condition("HAVING", &having.this)?;
4610        }
4611
4612        // QUALIFY and WINDOW clause ordering depends on input SQL
4613        if select.qualify_after_window {
4614            // WINDOW before QUALIFY (DuckDB style)
4615            if let Some(windows) = &select.windows {
4616                self.write_window_clause(windows)?;
4617            }
4618            if let Some(qualify) = &select.qualify {
4619                self.write_clause_condition("QUALIFY", &qualify.this)?;
4620            }
4621        } else {
4622            // QUALIFY before WINDOW (Snowflake/BigQuery default)
4623            if let Some(qualify) = &select.qualify {
4624                self.write_clause_condition("QUALIFY", &qualify.this)?;
4625            }
4626            if let Some(windows) = &select.windows {
4627                self.write_window_clause(windows)?;
4628            }
4629        }
4630
4631        // DISTRIBUTE BY (Hive/Spark)
4632        if let Some(distribute_by) = &select.distribute_by {
4633            self.write_clause_expressions("DISTRIBUTE BY", &distribute_by.expressions)?;
4634        }
4635
4636        // CLUSTER BY (Hive/Spark)
4637        if let Some(cluster_by) = &select.cluster_by {
4638            self.write_order_clause("CLUSTER BY", &cluster_by.expressions)?;
4639        }
4640
4641        // SORT BY (Hive/Spark - comes before ORDER BY)
4642        if let Some(sort_by) = &select.sort_by {
4643            self.write_order_clause("SORT BY", &sort_by.expressions)?;
4644        }
4645
4646        // ORDER BY (or ORDER SIBLINGS BY for Oracle hierarchical queries)
4647        if let Some(order_by) = &select.order_by {
4648            if self.config.pretty {
4649                // Output leading comments on their own lines before ORDER BY
4650                for comment in &order_by.comments {
4651                    self.write_newline();
4652                    self.write_indent();
4653                    self.write_formatted_comment(comment);
4654                }
4655            } else {
4656                for comment in &order_by.comments {
4657                    self.write_space();
4658                    self.write_formatted_comment(comment);
4659                }
4660            }
4661            let keyword = if order_by.siblings {
4662                "ORDER SIBLINGS BY"
4663            } else {
4664                "ORDER BY"
4665            };
4666            self.write_order_clause(keyword, &order_by.expressions)?;
4667        }
4668
4669        // TSQL: FETCH requires ORDER BY. If there's a FETCH but no ORDER BY, add ORDER BY (SELECT NULL) OFFSET 0 ROWS
4670        if select.order_by.is_none()
4671            && select.fetch.is_some()
4672            && matches!(
4673                self.config.dialect,
4674                Some(DialectType::TSQL) | Some(DialectType::Fabric)
4675            )
4676        {
4677            if self.config.pretty {
4678                self.write_newline();
4679                self.write_indent();
4680            } else {
4681                self.write_space();
4682            }
4683            self.write_keyword("ORDER BY (SELECT NULL) OFFSET 0 ROWS");
4684        }
4685
4686        // LIMIT and OFFSET
4687        // PostgreSQL and others use: LIMIT count OFFSET offset
4688        // SQL Server uses: OFFSET ... FETCH (no LIMIT)
4689        // Presto/Trino uses: OFFSET n LIMIT m (offset before limit)
4690        let is_presto_like = matches!(
4691            self.config.dialect,
4692            Some(DialectType::Presto) | Some(DialectType::Trino)
4693        );
4694
4695        if is_presto_like && select.offset.is_some() {
4696            // Presto/Trino syntax: OFFSET n LIMIT m (offset comes first)
4697            if let Some(offset) = &select.offset {
4698                if self.config.pretty {
4699                    self.write_newline();
4700                    self.write_indent();
4701                } else {
4702                    self.write_space();
4703                }
4704                self.write_keyword("OFFSET");
4705                self.write_space();
4706                self.write_limit_expr(&offset.this)?;
4707                if offset.rows == Some(true) {
4708                    self.write_space();
4709                    self.write_keyword("ROWS");
4710                }
4711            }
4712            if let Some(limit) = &select.limit {
4713                if self.config.pretty {
4714                    self.write_newline();
4715                    self.write_indent();
4716                } else {
4717                    self.write_space();
4718                }
4719                self.write_keyword("LIMIT");
4720                self.write_space();
4721                self.write_limit_expr(&limit.this)?;
4722                if limit.percent {
4723                    self.write_space();
4724                    self.write_keyword("PERCENT");
4725                }
4726                // Emit any comments that were captured from before the LIMIT keyword
4727                for comment in &limit.comments {
4728                    self.write(" ");
4729                    self.write_formatted_comment(comment);
4730                }
4731            }
4732        } else {
4733            // Check if FETCH will be converted to LIMIT (used for ordering)
4734            let fetch_as_limit = select.fetch.as_ref().map_or(false, |fetch| {
4735                !fetch.percent
4736                    && !fetch.with_ties
4737                    && fetch.count.is_some()
4738                    && matches!(
4739                        self.config.dialect,
4740                        Some(DialectType::Spark)
4741                            | Some(DialectType::Hive)
4742                            | Some(DialectType::DuckDB)
4743                            | Some(DialectType::SQLite)
4744                            | Some(DialectType::MySQL)
4745                            | Some(DialectType::BigQuery)
4746                            | Some(DialectType::Databricks)
4747                            | Some(DialectType::StarRocks)
4748                            | Some(DialectType::Doris)
4749                            | Some(DialectType::Athena)
4750                            | Some(DialectType::ClickHouse)
4751                            | Some(DialectType::Redshift)
4752                    )
4753            });
4754
4755            // Standard LIMIT clause (skip for SQL Server - we use TOP or OFFSET/FETCH instead)
4756            if let Some(limit) = &select.limit {
4757                // SQL Server uses TOP (no OFFSET) or OFFSET/FETCH (with OFFSET) instead of LIMIT
4758                if !matches!(
4759                    self.config.dialect,
4760                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
4761                ) {
4762                    if self.config.pretty {
4763                        self.write_newline();
4764                        self.write_indent();
4765                    } else {
4766                        self.write_space();
4767                    }
4768                    self.write_keyword("LIMIT");
4769                    self.write_space();
4770                    self.write_limit_expr(&limit.this)?;
4771                    if limit.percent {
4772                        self.write_space();
4773                        self.write_keyword("PERCENT");
4774                    }
4775                    // Emit any comments that were captured from before the LIMIT keyword
4776                    for comment in &limit.comments {
4777                        self.write(" ");
4778                        self.write_formatted_comment(comment);
4779                    }
4780                }
4781            }
4782
4783            // Convert TOP to LIMIT for non-TOP dialects
4784            if select.top.is_some() && !is_top_dialect && select.limit.is_none() {
4785                if let Some(top) = &select.top {
4786                    if !top.percent && !top.with_ties {
4787                        if self.config.pretty {
4788                            self.write_newline();
4789                            self.write_indent();
4790                        } else {
4791                            self.write_space();
4792                        }
4793                        self.write_keyword("LIMIT");
4794                        self.write_space();
4795                        self.generate_expression(&top.this)?;
4796                    }
4797                }
4798            }
4799
4800            // If FETCH will be converted to LIMIT and there's also OFFSET,
4801            // emit LIMIT from FETCH BEFORE the OFFSET
4802            if fetch_as_limit && select.offset.is_some() {
4803                if let Some(fetch) = &select.fetch {
4804                    if self.config.pretty {
4805                        self.write_newline();
4806                        self.write_indent();
4807                    } else {
4808                        self.write_space();
4809                    }
4810                    self.write_keyword("LIMIT");
4811                    self.write_space();
4812                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4813                }
4814            }
4815
4816            // OFFSET
4817            // In SQL Server, OFFSET requires ORDER BY and uses different syntax
4818            // OFFSET x ROWS FETCH NEXT y ROWS ONLY
4819            if let Some(offset) = &select.offset {
4820                if self.config.pretty {
4821                    self.write_newline();
4822                    self.write_indent();
4823                } else {
4824                    self.write_space();
4825                }
4826                if matches!(
4827                    self.config.dialect,
4828                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
4829                ) {
4830                    // SQL Server 2012+ OFFSET ... FETCH syntax
4831                    self.write_keyword("OFFSET");
4832                    self.write_space();
4833                    self.write_limit_expr(&offset.this)?;
4834                    self.write_space();
4835                    self.write_keyword("ROWS");
4836                    // If there was a LIMIT, use FETCH NEXT ... ROWS ONLY
4837                    if let Some(limit) = &select.limit {
4838                        self.write_space();
4839                        self.write_keyword("FETCH NEXT");
4840                        self.write_space();
4841                        self.write_limit_expr(&limit.this)?;
4842                        self.write_space();
4843                        self.write_keyword("ROWS ONLY");
4844                    }
4845                } else {
4846                    self.write_keyword("OFFSET");
4847                    self.write_space();
4848                    self.write_limit_expr(&offset.this)?;
4849                    // Output ROWS keyword if it was in the original SQL
4850                    if offset.rows == Some(true) {
4851                        self.write_space();
4852                        self.write_keyword("ROWS");
4853                    }
4854                }
4855            }
4856        }
4857
4858        // ClickHouse LIMIT BY clause (after LIMIT/OFFSET)
4859        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4860            if let Some(limit_by) = &select.limit_by {
4861                if !limit_by.is_empty() {
4862                    self.write_space();
4863                    self.write_keyword("BY");
4864                    self.write_space();
4865                    for (i, expr) in limit_by.iter().enumerate() {
4866                        if i > 0 {
4867                            self.write(", ");
4868                        }
4869                        self.generate_expression(expr)?;
4870                    }
4871                }
4872            }
4873        }
4874
4875        // ClickHouse SETTINGS and FORMAT modifiers (after LIMIT/OFFSET)
4876        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
4877            if let Some(settings) = &select.settings {
4878                if self.config.pretty {
4879                    self.write_newline();
4880                    self.write_indent();
4881                } else {
4882                    self.write_space();
4883                }
4884                self.write_keyword("SETTINGS");
4885                self.write_space();
4886                for (i, expr) in settings.iter().enumerate() {
4887                    if i > 0 {
4888                        self.write(", ");
4889                    }
4890                    self.generate_expression(expr)?;
4891                }
4892            }
4893
4894            if let Some(format_expr) = &select.format {
4895                if self.config.pretty {
4896                    self.write_newline();
4897                    self.write_indent();
4898                } else {
4899                    self.write_space();
4900                }
4901                self.write_keyword("FORMAT");
4902                self.write_space();
4903                self.generate_expression(format_expr)?;
4904            }
4905        }
4906
4907        // FETCH FIRST/NEXT
4908        if let Some(fetch) = &select.fetch {
4909            // Check if we already emitted LIMIT from FETCH before OFFSET
4910            let fetch_already_as_limit = select.offset.is_some()
4911                && !fetch.percent
4912                && !fetch.with_ties
4913                && fetch.count.is_some()
4914                && matches!(
4915                    self.config.dialect,
4916                    Some(DialectType::Spark)
4917                        | Some(DialectType::Hive)
4918                        | Some(DialectType::DuckDB)
4919                        | Some(DialectType::SQLite)
4920                        | Some(DialectType::MySQL)
4921                        | Some(DialectType::BigQuery)
4922                        | Some(DialectType::Databricks)
4923                        | Some(DialectType::StarRocks)
4924                        | Some(DialectType::Doris)
4925                        | Some(DialectType::Athena)
4926                        | Some(DialectType::ClickHouse)
4927                        | Some(DialectType::Redshift)
4928                );
4929
4930            if fetch_already_as_limit {
4931                // Already emitted as LIMIT before OFFSET, skip
4932            } else {
4933                if self.config.pretty {
4934                    self.write_newline();
4935                    self.write_indent();
4936                } else {
4937                    self.write_space();
4938                }
4939
4940                // Convert FETCH to LIMIT for dialects that prefer LIMIT syntax
4941                let use_limit = !fetch.percent
4942                    && !fetch.with_ties
4943                    && fetch.count.is_some()
4944                    && matches!(
4945                        self.config.dialect,
4946                        Some(DialectType::Spark)
4947                            | Some(DialectType::Hive)
4948                            | Some(DialectType::DuckDB)
4949                            | Some(DialectType::SQLite)
4950                            | Some(DialectType::MySQL)
4951                            | Some(DialectType::BigQuery)
4952                            | Some(DialectType::Databricks)
4953                            | Some(DialectType::StarRocks)
4954                            | Some(DialectType::Doris)
4955                            | Some(DialectType::Athena)
4956                            | Some(DialectType::ClickHouse)
4957                            | Some(DialectType::Redshift)
4958                    );
4959
4960                if use_limit {
4961                    self.write_keyword("LIMIT");
4962                    self.write_space();
4963                    self.generate_expression(fetch.count.as_ref().unwrap())?;
4964                } else {
4965                    self.write_keyword("FETCH");
4966                    self.write_space();
4967                    self.write_keyword(&fetch.direction);
4968                    if let Some(ref count) = fetch.count {
4969                        self.write_space();
4970                        self.generate_expression(count)?;
4971                    }
4972                    if fetch.percent {
4973                        self.write_space();
4974                        self.write_keyword("PERCENT");
4975                    }
4976                    if fetch.rows {
4977                        self.write_space();
4978                        self.write_keyword("ROWS");
4979                    }
4980                    if fetch.with_ties {
4981                        self.write_space();
4982                        self.write_keyword("WITH TIES");
4983                    } else {
4984                        self.write_space();
4985                        self.write_keyword("ONLY");
4986                    }
4987                }
4988            } // close fetch_already_as_limit else
4989        }
4990
4991        // SAMPLE / TABLESAMPLE
4992        if let Some(sample) = &select.sample {
4993            use crate::dialects::DialectType;
4994            if self.config.pretty {
4995                self.write_newline();
4996            } else {
4997                self.write_space();
4998            }
4999
5000            if sample.is_using_sample {
5001                // DuckDB USING SAMPLE: METHOD (size UNIT) [REPEATABLE (seed)]
5002                self.write_keyword("USING SAMPLE");
5003                self.generate_sample_body(sample)?;
5004            } else {
5005                self.write_keyword("TABLESAMPLE");
5006
5007                // Snowflake defaults to BERNOULLI when no explicit method is given
5008                let snowflake_bernoulli =
5009                    matches!(self.config.dialect, Some(DialectType::Snowflake))
5010                        && !sample.explicit_method;
5011                if snowflake_bernoulli {
5012                    self.write_space();
5013                    self.write_keyword("BERNOULLI");
5014                }
5015
5016                // Handle BUCKET sampling: TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)
5017                if matches!(sample.method, SampleMethod::Bucket) {
5018                    self.write_space();
5019                    self.write("(");
5020                    self.write_keyword("BUCKET");
5021                    self.write_space();
5022                    if let Some(ref num) = sample.bucket_numerator {
5023                        self.generate_expression(num)?;
5024                    }
5025                    self.write_space();
5026                    self.write_keyword("OUT OF");
5027                    self.write_space();
5028                    if let Some(ref denom) = sample.bucket_denominator {
5029                        self.generate_expression(denom)?;
5030                    }
5031                    if let Some(ref field) = sample.bucket_field {
5032                        self.write_space();
5033                        self.write_keyword("ON");
5034                        self.write_space();
5035                        self.generate_expression(field)?;
5036                    }
5037                    self.write(")");
5038                } else if sample.unit_after_size {
5039                    // Syntax: TABLESAMPLE [METHOD] (size ROWS) or TABLESAMPLE [METHOD] (size PERCENT)
5040                    if sample.explicit_method && sample.method_before_size {
5041                        self.write_space();
5042                        match sample.method {
5043                            SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5044                            SampleMethod::System => self.write_keyword("SYSTEM"),
5045                            SampleMethod::Block => self.write_keyword("BLOCK"),
5046                            SampleMethod::Row => self.write_keyword("ROW"),
5047                            SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5048                            _ => {}
5049                        }
5050                    }
5051                    self.write(" (");
5052                    self.generate_expression(&sample.size)?;
5053                    self.write_space();
5054                    match sample.method {
5055                        SampleMethod::Percent => self.write_keyword("PERCENT"),
5056                        SampleMethod::Row => self.write_keyword("ROWS"),
5057                        SampleMethod::Reservoir => self.write_keyword("ROWS"),
5058                        _ => {
5059                            self.write_keyword("PERCENT");
5060                        }
5061                    }
5062                    self.write(")");
5063                } else {
5064                    // Syntax: TABLESAMPLE METHOD (size)
5065                    self.write_space();
5066                    match sample.method {
5067                        SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
5068                        SampleMethod::System => self.write_keyword("SYSTEM"),
5069                        SampleMethod::Block => self.write_keyword("BLOCK"),
5070                        SampleMethod::Row => self.write_keyword("ROW"),
5071                        SampleMethod::Percent => self.write_keyword("BERNOULLI"),
5072                        SampleMethod::Bucket => {}
5073                        SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
5074                    }
5075                    self.write(" (");
5076                    self.generate_expression(&sample.size)?;
5077                    if matches!(sample.method, SampleMethod::Percent) {
5078                        self.write_space();
5079                        self.write_keyword("PERCENT");
5080                    }
5081                    self.write(")");
5082                }
5083            }
5084
5085            if let Some(seed) = &sample.seed {
5086                self.write_space();
5087                // Databricks/Spark use REPEATABLE, not SEED
5088                let use_seed = sample.use_seed_keyword
5089                    && !matches!(
5090                        self.config.dialect,
5091                        Some(crate::dialects::DialectType::Databricks)
5092                            | Some(crate::dialects::DialectType::Spark)
5093                    );
5094                if use_seed {
5095                    self.write_keyword("SEED");
5096                } else {
5097                    self.write_keyword("REPEATABLE");
5098                }
5099                self.write(" (");
5100                self.generate_expression(seed)?;
5101                self.write(")");
5102            }
5103        }
5104
5105        // FOR UPDATE/SHARE locks
5106        // Skip locking clauses for dialects that don't support them
5107        if self.config.locking_reads_supported {
5108            for lock in &select.locks {
5109                if self.config.pretty {
5110                    self.write_newline();
5111                    self.write_indent();
5112                } else {
5113                    self.write_space();
5114                }
5115                self.generate_lock(lock)?;
5116            }
5117        }
5118
5119        // FOR XML clause (T-SQL)
5120        if !select.for_xml.is_empty() {
5121            if self.config.pretty {
5122                self.write_newline();
5123                self.write_indent();
5124            } else {
5125                self.write_space();
5126            }
5127            self.write_keyword("FOR XML");
5128            for (i, opt) in select.for_xml.iter().enumerate() {
5129                if self.config.pretty {
5130                    if i > 0 {
5131                        self.write(",");
5132                    }
5133                    self.write_newline();
5134                    self.write_indent();
5135                    self.write("  "); // extra indent for options
5136                } else {
5137                    if i > 0 {
5138                        self.write(",");
5139                    }
5140                    self.write_space();
5141                }
5142                self.generate_for_xml_option(opt)?;
5143            }
5144        }
5145
5146        // FOR JSON clause (T-SQL)
5147        if !select.for_json.is_empty()
5148            && matches!(
5149                self.config.dialect,
5150                None | Some(DialectType::TSQL) | Some(DialectType::Fabric)
5151            )
5152        {
5153            if self.config.pretty {
5154                self.write_newline();
5155                self.write_indent();
5156            } else {
5157                self.write_space();
5158            }
5159            self.write_keyword("FOR JSON");
5160            for (i, opt) in select.for_json.iter().enumerate() {
5161                if self.config.pretty {
5162                    if i > 0 {
5163                        self.write(",");
5164                    }
5165                    self.write_newline();
5166                    self.write_indent();
5167                    self.write("  "); // extra indent for options
5168                } else {
5169                    if i > 0 {
5170                        self.write(",");
5171                    }
5172                    self.write_space();
5173                }
5174                self.generate_for_xml_option(opt)?;
5175            }
5176        }
5177
5178        // TSQL: OPTION clause
5179        if let Some(ref option) = select.option {
5180            if matches!(
5181                self.config.dialect,
5182                Some(crate::dialects::DialectType::TSQL)
5183                    | Some(crate::dialects::DialectType::Fabric)
5184            ) {
5185                self.write_space();
5186                self.write(option);
5187            }
5188        }
5189
5190        Ok(())
5191    }
5192
5193    /// Generate a single FOR XML option
5194    fn generate_for_xml_option(&mut self, opt: &Expression) -> Result<()> {
5195        match opt {
5196            Expression::QueryOption(qo) => {
5197                // Extract the option name from Var
5198                if let Expression::Var(var) = &*qo.this {
5199                    self.write(&var.this);
5200                } else {
5201                    self.generate_expression(&qo.this)?;
5202                }
5203                // If there's an expression (like PATH('element')), output it in parens
5204                if let Some(expr) = &qo.expression {
5205                    self.write("(");
5206                    self.generate_expression(expr)?;
5207                    self.write(")");
5208                }
5209            }
5210            _ => {
5211                self.generate_expression(opt)?;
5212            }
5213        }
5214        Ok(())
5215    }
5216
5217    fn generate_with(&mut self, with: &With) -> Result<()> {
5218        use crate::dialects::DialectType;
5219
5220        // Output leading comments before WITH
5221        for comment in &with.leading_comments {
5222            self.write_formatted_comment(comment);
5223            self.write(" ");
5224        }
5225        self.write_keyword("WITH");
5226        if with.recursive && self.config.cte_recursive_keyword_required {
5227            self.write_space();
5228            self.write_keyword("RECURSIVE");
5229        }
5230        self.write_space();
5231
5232        // BigQuery doesn't support column aliases in CTE definitions
5233        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
5234
5235        for (i, cte) in with.ctes.iter().enumerate() {
5236            if i > 0 {
5237                self.write(",");
5238                if self.config.pretty {
5239                    self.write_space();
5240                } else {
5241                    self.write(" ");
5242                }
5243            }
5244            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !cte.alias_first {
5245                self.generate_expression(&cte.this)?;
5246                self.write_space();
5247                self.write_keyword("AS");
5248                self.write_space();
5249                self.generate_identifier(&cte.alias)?;
5250                continue;
5251            }
5252            self.generate_identifier(&cte.alias)?;
5253            // Output CTE comments after alias name, before AS
5254            for comment in &cte.comments {
5255                self.write_space();
5256                self.write_formatted_comment(comment);
5257            }
5258            if !cte.columns.is_empty() && !skip_cte_columns {
5259                self.write("(");
5260                for (j, col) in cte.columns.iter().enumerate() {
5261                    if j > 0 {
5262                        self.write(", ");
5263                    }
5264                    self.generate_identifier(col)?;
5265                }
5266                self.write(")");
5267            }
5268            // USING KEY (columns) for DuckDB recursive CTEs
5269            if !cte.key_expressions.is_empty() {
5270                self.write_space();
5271                self.write_keyword("USING KEY");
5272                self.write(" (");
5273                for (i, key) in cte.key_expressions.iter().enumerate() {
5274                    if i > 0 {
5275                        self.write(", ");
5276                    }
5277                    self.generate_identifier(key)?;
5278                }
5279                self.write(")");
5280            }
5281            self.write_space();
5282            self.write_keyword("AS");
5283            // MATERIALIZED / NOT MATERIALIZED
5284            if let Some(materialized) = cte.materialized {
5285                self.write_space();
5286                if materialized {
5287                    self.write_keyword("MATERIALIZED");
5288                } else {
5289                    self.write_keyword("NOT MATERIALIZED");
5290                }
5291            }
5292            self.write(" (");
5293            if self.config.pretty {
5294                self.write_newline();
5295                self.indent_level += 1;
5296                self.write_indent();
5297            }
5298            // For Spark/Databricks, VALUES in a CTE must be wrapped with SELECT * FROM
5299            // e.g., WITH t AS (VALUES ('foo_val') AS t(foo1)) -> WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1))
5300            let wrap_values_in_select = matches!(
5301                self.config.dialect,
5302                Some(DialectType::Spark) | Some(DialectType::Databricks)
5303            ) && matches!(&cte.this, Expression::Values(_));
5304
5305            if wrap_values_in_select {
5306                self.write_keyword("SELECT");
5307                self.write(" * ");
5308                self.write_keyword("FROM");
5309                self.write_space();
5310            }
5311            self.generate_expression(&cte.this)?;
5312            if self.config.pretty {
5313                self.write_newline();
5314                self.indent_level -= 1;
5315                self.write_indent();
5316            }
5317            self.write(")");
5318        }
5319
5320        // Generate SEARCH/CYCLE clause if present
5321        if let Some(search) = &with.search {
5322            self.write_space();
5323            self.generate_expression(search)?;
5324        }
5325
5326        Ok(())
5327    }
5328
5329    /// Generate joins with proper nesting structure for pretty printing.
5330    /// Deferred-condition joins "own" the non-deferred joins that follow them
5331    /// within the same nesting_group.
5332    fn generate_joins_with_nesting(&mut self, joins: &[Join]) -> Result<()> {
5333        let mut i = 0;
5334        while i < joins.len() {
5335            if joins[i].deferred_condition {
5336                let parent_group = joins[i].nesting_group;
5337
5338                // This join owns the following non-deferred joins in the same nesting_group
5339                // First output the join keyword and table (without condition)
5340                self.generate_join_without_condition(&joins[i])?;
5341
5342                // Find the range of child joins: same nesting_group and not deferred
5343                let child_start = i + 1;
5344                let mut child_end = child_start;
5345                while child_end < joins.len()
5346                    && !joins[child_end].deferred_condition
5347                    && joins[child_end].nesting_group == parent_group
5348                {
5349                    child_end += 1;
5350                }
5351
5352                // Output child joins with extra indentation
5353                if child_start < child_end {
5354                    self.indent_level += 1;
5355                    for j in child_start..child_end {
5356                        self.generate_join(&joins[j])?;
5357                    }
5358                    self.indent_level -= 1;
5359                }
5360
5361                // Output the deferred condition at the parent level
5362                self.generate_join_condition(&joins[i])?;
5363
5364                i = child_end;
5365            } else {
5366                // Regular join (no nesting)
5367                self.generate_join(&joins[i])?;
5368                i += 1;
5369            }
5370        }
5371        Ok(())
5372    }
5373
5374    /// Generate a join's keyword and table reference, but not its ON/USING condition.
5375    /// Used for deferred-condition joins where the condition is output after child joins.
5376    fn generate_join_without_condition(&mut self, join: &Join) -> Result<()> {
5377        // Save and temporarily clear the condition to prevent generate_join from outputting it
5378        // We achieve this by creating a modified copy
5379        let mut join_copy = join.clone();
5380        join_copy.on = None;
5381        join_copy.using = Vec::new();
5382        join_copy.deferred_condition = false;
5383        self.generate_join(&join_copy)
5384    }
5385
5386    fn generate_join(&mut self, join: &Join) -> Result<()> {
5387        // Implicit (comma) joins: output as ", table" instead of "CROSS JOIN table"
5388        if join.kind == JoinKind::Implicit {
5389            self.write(",");
5390            if self.config.pretty {
5391                self.write_newline();
5392                self.write_indent();
5393            } else {
5394                self.write_space();
5395            }
5396            self.generate_expression(&join.this)?;
5397            return Ok(());
5398        }
5399
5400        if self.config.pretty {
5401            self.write_newline();
5402            self.write_indent();
5403        } else {
5404            self.write_space();
5405        }
5406
5407        // Helper: format hint suffix (e.g., " LOOP" or "")
5408        // Only include join hints for dialects that support them
5409        let hint_str = if self.config.join_hints {
5410            join.join_hint
5411                .as_ref()
5412                .map(|h| format!(" {}", h))
5413                .unwrap_or_default()
5414        } else {
5415            String::new()
5416        };
5417
5418        let clickhouse_join_keyword =
5419            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
5420                if let Some(hint) = &join.join_hint {
5421                    let mut global = false;
5422                    let mut strictness: Option<&'static str> = None;
5423                    for part in hint.split_whitespace() {
5424                        if part.eq_ignore_ascii_case("GLOBAL") {
5425                            global = true;
5426                        } else if part.eq_ignore_ascii_case("ALL") {
5427                            strictness = Some("ALL");
5428                        } else if part.eq_ignore_ascii_case("ANY") {
5429                            strictness = Some("ANY");
5430                        } else if part.eq_ignore_ascii_case("ASOF") {
5431                            strictness = Some("ASOF");
5432                        } else if part.eq_ignore_ascii_case("SEMI") {
5433                            strictness = Some("SEMI");
5434                        } else if part.eq_ignore_ascii_case("ANTI") {
5435                            strictness = Some("ANTI");
5436                        }
5437                    }
5438
5439                    if global || strictness.is_some() {
5440                        let join_type = match join.kind {
5441                            JoinKind::Left => {
5442                                if join.use_outer_keyword {
5443                                    "LEFT OUTER"
5444                                } else if join.use_inner_keyword {
5445                                    "LEFT INNER"
5446                                } else {
5447                                    "LEFT"
5448                                }
5449                            }
5450                            JoinKind::Right => {
5451                                if join.use_outer_keyword {
5452                                    "RIGHT OUTER"
5453                                } else if join.use_inner_keyword {
5454                                    "RIGHT INNER"
5455                                } else {
5456                                    "RIGHT"
5457                                }
5458                            }
5459                            JoinKind::Full => {
5460                                if join.use_outer_keyword {
5461                                    "FULL OUTER"
5462                                } else {
5463                                    "FULL"
5464                                }
5465                            }
5466                            JoinKind::Inner => {
5467                                if join.use_inner_keyword {
5468                                    "INNER"
5469                                } else {
5470                                    ""
5471                                }
5472                            }
5473                            _ => "",
5474                        };
5475
5476                        let mut parts = Vec::new();
5477                        if global {
5478                            parts.push("GLOBAL");
5479                        }
5480                        if !join_type.is_empty() {
5481                            parts.push(join_type);
5482                        }
5483                        if let Some(strict) = strictness {
5484                            parts.push(strict);
5485                        }
5486                        parts.push("JOIN");
5487                        Some(parts.join(" "))
5488                    } else {
5489                        None
5490                    }
5491                } else {
5492                    None
5493                }
5494            } else {
5495                None
5496            };
5497
5498        // Output any comments associated with this join
5499        // In pretty mode, comments go on their own line before the join keyword
5500        // In non-pretty mode, comments go inline before the join keyword
5501        if !join.comments.is_empty() {
5502            if self.config.pretty {
5503                // In pretty mode, go back before the newline+indent we just wrote
5504                // and output comments on their own lines
5505                // We need to output comments BEFORE the join keyword on separate lines
5506                // Trim the trailing newline+indent we already wrote
5507                let trimmed = self.output.trim_end().len();
5508                self.output.truncate(trimmed);
5509                for comment in &join.comments {
5510                    self.write_newline();
5511                    self.write_indent();
5512                    self.write_formatted_comment(comment);
5513                }
5514                self.write_newline();
5515                self.write_indent();
5516            } else {
5517                for comment in &join.comments {
5518                    self.write_formatted_comment(comment);
5519                    self.write_space();
5520                }
5521            }
5522        }
5523
5524        let directed_str = if join.directed { " DIRECTED" } else { "" };
5525
5526        if let Some(keyword) = clickhouse_join_keyword {
5527            self.write_keyword(&keyword);
5528        } else {
5529            match join.kind {
5530                JoinKind::Inner => {
5531                    if join.use_inner_keyword {
5532                        if hint_str.is_empty() && directed_str.is_empty() {
5533                            self.write_keyword("INNER JOIN");
5534                        } else {
5535                            self.write_keyword("INNER");
5536                            if !hint_str.is_empty() {
5537                                self.write_keyword(&hint_str);
5538                            }
5539                            if !directed_str.is_empty() {
5540                                self.write_keyword(directed_str);
5541                            }
5542                            self.write_keyword(" JOIN");
5543                        }
5544                    } else {
5545                        if !hint_str.is_empty() {
5546                            self.write_keyword(hint_str.trim());
5547                            self.write_keyword(" ");
5548                        }
5549                        if !directed_str.is_empty() {
5550                            self.write_keyword("DIRECTED ");
5551                        }
5552                        self.write_keyword("JOIN");
5553                    }
5554                }
5555                JoinKind::Left => {
5556                    if join.use_outer_keyword {
5557                        if hint_str.is_empty() && directed_str.is_empty() {
5558                            self.write_keyword("LEFT OUTER JOIN");
5559                        } else {
5560                            self.write_keyword("LEFT OUTER");
5561                            if !hint_str.is_empty() {
5562                                self.write_keyword(&hint_str);
5563                            }
5564                            if !directed_str.is_empty() {
5565                                self.write_keyword(directed_str);
5566                            }
5567                            self.write_keyword(" JOIN");
5568                        }
5569                    } else if join.use_inner_keyword {
5570                        if hint_str.is_empty() && directed_str.is_empty() {
5571                            self.write_keyword("LEFT INNER JOIN");
5572                        } else {
5573                            self.write_keyword("LEFT INNER");
5574                            if !hint_str.is_empty() {
5575                                self.write_keyword(&hint_str);
5576                            }
5577                            if !directed_str.is_empty() {
5578                                self.write_keyword(directed_str);
5579                            }
5580                            self.write_keyword(" JOIN");
5581                        }
5582                    } else {
5583                        if hint_str.is_empty() && directed_str.is_empty() {
5584                            self.write_keyword("LEFT JOIN");
5585                        } else {
5586                            self.write_keyword("LEFT");
5587                            if !hint_str.is_empty() {
5588                                self.write_keyword(&hint_str);
5589                            }
5590                            if !directed_str.is_empty() {
5591                                self.write_keyword(directed_str);
5592                            }
5593                            self.write_keyword(" JOIN");
5594                        }
5595                    }
5596                }
5597                JoinKind::Right => {
5598                    if join.use_outer_keyword {
5599                        if hint_str.is_empty() && directed_str.is_empty() {
5600                            self.write_keyword("RIGHT OUTER JOIN");
5601                        } else {
5602                            self.write_keyword("RIGHT OUTER");
5603                            if !hint_str.is_empty() {
5604                                self.write_keyword(&hint_str);
5605                            }
5606                            if !directed_str.is_empty() {
5607                                self.write_keyword(directed_str);
5608                            }
5609                            self.write_keyword(" JOIN");
5610                        }
5611                    } else if join.use_inner_keyword {
5612                        if hint_str.is_empty() && directed_str.is_empty() {
5613                            self.write_keyword("RIGHT INNER JOIN");
5614                        } else {
5615                            self.write_keyword("RIGHT INNER");
5616                            if !hint_str.is_empty() {
5617                                self.write_keyword(&hint_str);
5618                            }
5619                            if !directed_str.is_empty() {
5620                                self.write_keyword(directed_str);
5621                            }
5622                            self.write_keyword(" JOIN");
5623                        }
5624                    } else {
5625                        if hint_str.is_empty() && directed_str.is_empty() {
5626                            self.write_keyword("RIGHT JOIN");
5627                        } else {
5628                            self.write_keyword("RIGHT");
5629                            if !hint_str.is_empty() {
5630                                self.write_keyword(&hint_str);
5631                            }
5632                            if !directed_str.is_empty() {
5633                                self.write_keyword(directed_str);
5634                            }
5635                            self.write_keyword(" JOIN");
5636                        }
5637                    }
5638                }
5639                JoinKind::Full => {
5640                    if join.use_outer_keyword {
5641                        if hint_str.is_empty() && directed_str.is_empty() {
5642                            self.write_keyword("FULL OUTER JOIN");
5643                        } else {
5644                            self.write_keyword("FULL OUTER");
5645                            if !hint_str.is_empty() {
5646                                self.write_keyword(&hint_str);
5647                            }
5648                            if !directed_str.is_empty() {
5649                                self.write_keyword(directed_str);
5650                            }
5651                            self.write_keyword(" JOIN");
5652                        }
5653                    } else {
5654                        if hint_str.is_empty() && directed_str.is_empty() {
5655                            self.write_keyword("FULL JOIN");
5656                        } else {
5657                            self.write_keyword("FULL");
5658                            if !hint_str.is_empty() {
5659                                self.write_keyword(&hint_str);
5660                            }
5661                            if !directed_str.is_empty() {
5662                                self.write_keyword(directed_str);
5663                            }
5664                            self.write_keyword(" JOIN");
5665                        }
5666                    }
5667                }
5668                JoinKind::Outer => {
5669                    if directed_str.is_empty() {
5670                        self.write_keyword("OUTER JOIN");
5671                    } else {
5672                        self.write_keyword("OUTER");
5673                        self.write_keyword(directed_str);
5674                        self.write_keyword(" JOIN");
5675                    }
5676                }
5677                JoinKind::Cross => {
5678                    if directed_str.is_empty() {
5679                        self.write_keyword("CROSS JOIN");
5680                    } else {
5681                        self.write_keyword("CROSS");
5682                        self.write_keyword(directed_str);
5683                        self.write_keyword(" JOIN");
5684                    }
5685                }
5686                JoinKind::Natural => {
5687                    if join.use_inner_keyword {
5688                        if directed_str.is_empty() {
5689                            self.write_keyword("NATURAL INNER JOIN");
5690                        } else {
5691                            self.write_keyword("NATURAL INNER");
5692                            self.write_keyword(directed_str);
5693                            self.write_keyword(" JOIN");
5694                        }
5695                    } else {
5696                        if directed_str.is_empty() {
5697                            self.write_keyword("NATURAL JOIN");
5698                        } else {
5699                            self.write_keyword("NATURAL");
5700                            self.write_keyword(directed_str);
5701                            self.write_keyword(" JOIN");
5702                        }
5703                    }
5704                }
5705                JoinKind::NaturalLeft => {
5706                    if join.use_outer_keyword {
5707                        if directed_str.is_empty() {
5708                            self.write_keyword("NATURAL LEFT OUTER JOIN");
5709                        } else {
5710                            self.write_keyword("NATURAL LEFT OUTER");
5711                            self.write_keyword(directed_str);
5712                            self.write_keyword(" JOIN");
5713                        }
5714                    } else {
5715                        if directed_str.is_empty() {
5716                            self.write_keyword("NATURAL LEFT JOIN");
5717                        } else {
5718                            self.write_keyword("NATURAL LEFT");
5719                            self.write_keyword(directed_str);
5720                            self.write_keyword(" JOIN");
5721                        }
5722                    }
5723                }
5724                JoinKind::NaturalRight => {
5725                    if join.use_outer_keyword {
5726                        if directed_str.is_empty() {
5727                            self.write_keyword("NATURAL RIGHT OUTER JOIN");
5728                        } else {
5729                            self.write_keyword("NATURAL RIGHT OUTER");
5730                            self.write_keyword(directed_str);
5731                            self.write_keyword(" JOIN");
5732                        }
5733                    } else {
5734                        if directed_str.is_empty() {
5735                            self.write_keyword("NATURAL RIGHT JOIN");
5736                        } else {
5737                            self.write_keyword("NATURAL RIGHT");
5738                            self.write_keyword(directed_str);
5739                            self.write_keyword(" JOIN");
5740                        }
5741                    }
5742                }
5743                JoinKind::NaturalFull => {
5744                    if join.use_outer_keyword {
5745                        if directed_str.is_empty() {
5746                            self.write_keyword("NATURAL FULL OUTER JOIN");
5747                        } else {
5748                            self.write_keyword("NATURAL FULL OUTER");
5749                            self.write_keyword(directed_str);
5750                            self.write_keyword(" JOIN");
5751                        }
5752                    } else {
5753                        if directed_str.is_empty() {
5754                            self.write_keyword("NATURAL FULL JOIN");
5755                        } else {
5756                            self.write_keyword("NATURAL FULL");
5757                            self.write_keyword(directed_str);
5758                            self.write_keyword(" JOIN");
5759                        }
5760                    }
5761                }
5762                JoinKind::Semi => self.write_keyword("SEMI JOIN"),
5763                JoinKind::Anti => self.write_keyword("ANTI JOIN"),
5764                JoinKind::LeftSemi => self.write_keyword("LEFT SEMI JOIN"),
5765                JoinKind::LeftAnti => self.write_keyword("LEFT ANTI JOIN"),
5766                JoinKind::RightSemi => self.write_keyword("RIGHT SEMI JOIN"),
5767                JoinKind::RightAnti => self.write_keyword("RIGHT ANTI JOIN"),
5768                JoinKind::CrossApply => {
5769                    // CROSS APPLY -> INNER JOIN LATERAL for non-TSQL-like dialects
5770                    if matches!(
5771                        self.config.dialect,
5772                        Some(DialectType::TSQL) | Some(DialectType::Fabric) | None
5773                    ) {
5774                        self.write_keyword("CROSS APPLY");
5775                    } else {
5776                        self.write_keyword("INNER JOIN LATERAL");
5777                    }
5778                }
5779                JoinKind::OuterApply => {
5780                    // OUTER APPLY -> LEFT JOIN LATERAL for non-TSQL-like dialects
5781                    if matches!(
5782                        self.config.dialect,
5783                        Some(DialectType::TSQL) | Some(DialectType::Fabric) | None
5784                    ) {
5785                        self.write_keyword("OUTER APPLY");
5786                    } else {
5787                        self.write_keyword("LEFT JOIN LATERAL");
5788                    }
5789                }
5790                JoinKind::AsOf => self.write_keyword("ASOF JOIN"),
5791                JoinKind::AsOfLeft => {
5792                    if join.use_outer_keyword {
5793                        self.write_keyword("ASOF LEFT OUTER JOIN");
5794                    } else {
5795                        self.write_keyword("ASOF LEFT JOIN");
5796                    }
5797                }
5798                JoinKind::AsOfRight => {
5799                    if join.use_outer_keyword {
5800                        self.write_keyword("ASOF RIGHT OUTER JOIN");
5801                    } else {
5802                        self.write_keyword("ASOF RIGHT JOIN");
5803                    }
5804                }
5805                JoinKind::Lateral => self.write_keyword("LATERAL JOIN"),
5806                JoinKind::LeftLateral => {
5807                    if join.use_outer_keyword {
5808                        self.write_keyword("LEFT OUTER LATERAL JOIN");
5809                    } else {
5810                        self.write_keyword("LEFT LATERAL JOIN");
5811                    }
5812                }
5813                JoinKind::Straight => self.write_keyword("STRAIGHT_JOIN"),
5814                JoinKind::Implicit => {
5815                    // BigQuery, Hive, Spark, and Databricks prefer explicit CROSS JOIN over comma syntax
5816                    // But only when source is the same dialect (identity) or source is another CROSS JOIN dialect
5817                    // When source is Generic, keep commas (Python sqlglot: parser marks joins, not generator)
5818                    use crate::dialects::DialectType;
5819                    let is_cj_dialect = matches!(
5820                        self.config.dialect,
5821                        Some(DialectType::BigQuery)
5822                            | Some(DialectType::Hive)
5823                            | Some(DialectType::Spark)
5824                            | Some(DialectType::Databricks)
5825                    );
5826                    let source_is_same = self.config.source_dialect.is_some()
5827                        && self.config.source_dialect == self.config.dialect;
5828                    let source_is_cj = matches!(
5829                        self.config.source_dialect,
5830                        Some(DialectType::BigQuery)
5831                            | Some(DialectType::Hive)
5832                            | Some(DialectType::Spark)
5833                            | Some(DialectType::Databricks)
5834                    );
5835                    if is_cj_dialect
5836                        && (source_is_same || source_is_cj || self.config.source_dialect.is_none())
5837                    {
5838                        self.write_keyword("CROSS JOIN");
5839                    } else {
5840                        // Implicit join uses comma: FROM a, b
5841                        // We already wrote a space before the match, so replace with comma
5842                        // by removing trailing space and writing ", "
5843                        self.output.truncate(self.output.trim_end().len());
5844                        self.write(",");
5845                    }
5846                }
5847                JoinKind::Array => self.write_keyword("ARRAY JOIN"),
5848                JoinKind::LeftArray => self.write_keyword("LEFT ARRAY JOIN"),
5849                JoinKind::Paste => self.write_keyword("PASTE JOIN"),
5850                JoinKind::Positional => self.write_keyword("POSITIONAL JOIN"),
5851            }
5852        }
5853
5854        // ARRAY JOIN items need comma-separated output (Tuple holds multiple items)
5855        if matches!(join.kind, JoinKind::Array | JoinKind::LeftArray) {
5856            match &join.this {
5857                Expression::Tuple(t) if t.expressions.is_empty() => {}
5858                Expression::Tuple(t) => {
5859                    self.write_space();
5860                    for (i, item) in t.expressions.iter().enumerate() {
5861                        if i > 0 {
5862                            self.write(", ");
5863                        }
5864                        self.generate_expression(item)?;
5865                    }
5866                }
5867                other => {
5868                    self.write_space();
5869                    self.generate_expression(other)?;
5870                }
5871            }
5872        } else {
5873            self.write_space();
5874            self.generate_expression(&join.this)?;
5875        }
5876
5877        // Only output MATCH_CONDITION/ON/USING inline if the condition wasn't deferred
5878        if !join.deferred_condition {
5879            // Output MATCH_CONDITION first (Snowflake ASOF JOIN)
5880            if let Some(match_cond) = &join.match_condition {
5881                self.write_space();
5882                self.write_keyword("MATCH_CONDITION");
5883                self.write(" (");
5884                self.generate_expression(match_cond)?;
5885                self.write(")");
5886            }
5887
5888            if let Some(on) = &join.on {
5889                if self.config.pretty {
5890                    self.write_newline();
5891                    self.indent_level += 1;
5892                    self.write_indent();
5893                    self.write_keyword("ON");
5894                    self.write_space();
5895                    self.generate_join_on_condition(on)?;
5896                    self.indent_level -= 1;
5897                } else {
5898                    self.write_space();
5899                    self.write_keyword("ON");
5900                    self.write_space();
5901                    self.generate_expression(on)?;
5902                }
5903            }
5904
5905            if !join.using.is_empty() {
5906                if self.config.pretty {
5907                    self.write_newline();
5908                    self.indent_level += 1;
5909                    self.write_indent();
5910                    self.write_keyword("USING");
5911                    self.write(" (");
5912                    for (i, col) in join.using.iter().enumerate() {
5913                        if i > 0 {
5914                            self.write(", ");
5915                        }
5916                        self.generate_identifier(col)?;
5917                    }
5918                    self.write(")");
5919                    self.indent_level -= 1;
5920                } else {
5921                    self.write_space();
5922                    self.write_keyword("USING");
5923                    self.write(" (");
5924                    for (i, col) in join.using.iter().enumerate() {
5925                        if i > 0 {
5926                            self.write(", ");
5927                        }
5928                        self.generate_identifier(col)?;
5929                    }
5930                    self.write(")");
5931                }
5932            }
5933        }
5934
5935        // Generate PIVOT/UNPIVOT expressions that follow this join
5936        for pivot in &join.pivots {
5937            self.write_space();
5938            self.generate_expression(pivot)?;
5939        }
5940
5941        Ok(())
5942    }
5943
5944    /// Generate just the ON/USING/MATCH_CONDITION for a join (used for deferred conditions)
5945    fn generate_join_condition(&mut self, join: &Join) -> Result<()> {
5946        // Generate MATCH_CONDITION first (Snowflake ASOF JOIN)
5947        if let Some(match_cond) = &join.match_condition {
5948            self.write_space();
5949            self.write_keyword("MATCH_CONDITION");
5950            self.write(" (");
5951            self.generate_expression(match_cond)?;
5952            self.write(")");
5953        }
5954
5955        if let Some(on) = &join.on {
5956            if self.config.pretty {
5957                self.write_newline();
5958                self.indent_level += 1;
5959                self.write_indent();
5960                self.write_keyword("ON");
5961                self.write_space();
5962                // In pretty mode, split AND conditions onto separate lines
5963                self.generate_join_on_condition(on)?;
5964                self.indent_level -= 1;
5965            } else {
5966                self.write_space();
5967                self.write_keyword("ON");
5968                self.write_space();
5969                self.generate_expression(on)?;
5970            }
5971        }
5972
5973        if !join.using.is_empty() {
5974            if self.config.pretty {
5975                self.write_newline();
5976                self.indent_level += 1;
5977                self.write_indent();
5978                self.write_keyword("USING");
5979                self.write(" (");
5980                for (i, col) in join.using.iter().enumerate() {
5981                    if i > 0 {
5982                        self.write(", ");
5983                    }
5984                    self.generate_identifier(col)?;
5985                }
5986                self.write(")");
5987                self.indent_level -= 1;
5988            } else {
5989                self.write_space();
5990                self.write_keyword("USING");
5991                self.write(" (");
5992                for (i, col) in join.using.iter().enumerate() {
5993                    if i > 0 {
5994                        self.write(", ");
5995                    }
5996                    self.generate_identifier(col)?;
5997                }
5998                self.write(")");
5999            }
6000        }
6001
6002        // Generate PIVOT/UNPIVOT expressions that follow this join (for deferred conditions)
6003        for pivot in &join.pivots {
6004            self.write_space();
6005            self.generate_expression(pivot)?;
6006        }
6007
6008        Ok(())
6009    }
6010
6011    /// Generate JOIN ON condition with AND clauses on separate lines in pretty mode
6012    fn generate_join_on_condition(&mut self, expr: &Expression) -> Result<()> {
6013        if let Expression::And(and_op) = expr {
6014            if let Some(conditions) = self.flatten_connector_terms(and_op, ConnectorOperator::And) {
6015                self.generate_expression(conditions[0])?;
6016                for condition in conditions.iter().skip(1) {
6017                    self.write_newline();
6018                    self.write_indent();
6019                    self.write_keyword("AND");
6020                    self.write_space();
6021                    self.generate_expression(condition)?;
6022                }
6023                return Ok(());
6024            }
6025        }
6026
6027        self.generate_expression(expr)
6028    }
6029
6030    fn generate_joined_table(&mut self, jt: &JoinedTable) -> Result<()> {
6031        // Parenthesized join: (tbl1 CROSS JOIN tbl2)
6032        self.write("(");
6033        self.generate_expression(&jt.left)?;
6034
6035        // Generate all joins
6036        for join in &jt.joins {
6037            self.generate_join(join)?;
6038        }
6039
6040        // Generate LATERAL VIEW clauses (Hive/Spark)
6041        for (lv_idx, lv) in jt.lateral_views.iter().enumerate() {
6042            self.generate_lateral_view(lv, lv_idx)?;
6043        }
6044
6045        self.write(")");
6046
6047        // Alias
6048        if let Some(alias) = &jt.alias {
6049            self.write_space();
6050            self.write_keyword("AS");
6051            self.write_space();
6052            self.generate_identifier(alias)?;
6053        }
6054
6055        Ok(())
6056    }
6057
6058    fn generate_lateral_view(&mut self, lv: &LateralView, lv_index: usize) -> Result<()> {
6059        use crate::dialects::DialectType;
6060
6061        if self.config.pretty {
6062            self.write_newline();
6063            self.write_indent();
6064        } else {
6065            self.write_space();
6066        }
6067
6068        // For Hive/Spark/Databricks (or no dialect specified), output native LATERAL VIEW syntax
6069        // For PostgreSQL and other specific dialects, convert to CROSS JOIN (LATERAL or UNNEST)
6070        let use_lateral_join = matches!(
6071            self.config.dialect,
6072            Some(DialectType::PostgreSQL)
6073                | Some(DialectType::DuckDB)
6074                | Some(DialectType::Snowflake)
6075                | Some(DialectType::TSQL)
6076                | Some(DialectType::Presto)
6077                | Some(DialectType::Trino)
6078                | Some(DialectType::Athena)
6079        );
6080
6081        // Check if target dialect should use UNNEST instead of EXPLODE
6082        let use_unnest = matches!(
6083            self.config.dialect,
6084            Some(DialectType::DuckDB)
6085                | Some(DialectType::Presto)
6086                | Some(DialectType::Trino)
6087                | Some(DialectType::Athena)
6088        );
6089
6090        // Check if we need POSEXPLODE -> UNNEST WITH ORDINALITY
6091        let (is_posexplode, is_inline, func_args) = match &lv.this {
6092            Expression::Explode(uf) => {
6093                // Expression::Explode is the dedicated EXPLODE expression type
6094                (false, false, vec![uf.this.clone()])
6095            }
6096            Expression::Unnest(uf) => {
6097                let mut args = vec![uf.this.clone()];
6098                args.extend(uf.expressions.clone());
6099                (false, false, args)
6100            }
6101            Expression::Function(func) => {
6102                if func.name.eq_ignore_ascii_case("POSEXPLODE")
6103                    || func.name.eq_ignore_ascii_case("POSEXPLODE_OUTER")
6104                {
6105                    (true, false, func.args.clone())
6106                } else if func.name.eq_ignore_ascii_case("INLINE") {
6107                    (false, true, func.args.clone())
6108                } else if func.name.eq_ignore_ascii_case("EXPLODE")
6109                    || func.name.eq_ignore_ascii_case("EXPLODE_OUTER")
6110                {
6111                    (false, false, func.args.clone())
6112                } else {
6113                    (false, false, vec![])
6114                }
6115            }
6116            _ => (false, false, vec![]),
6117        };
6118
6119        if use_lateral_join {
6120            // Convert to CROSS JOIN for PostgreSQL-like dialects
6121            if lv.outer {
6122                self.write_keyword("LEFT JOIN LATERAL");
6123            } else {
6124                self.write_keyword("CROSS JOIN");
6125            }
6126            self.write_space();
6127
6128            if use_unnest && !func_args.is_empty() {
6129                // Convert EXPLODE(y) -> UNNEST(y), POSEXPLODE(y) -> UNNEST(y)
6130                // For DuckDB, also convert ARRAY(y) -> [y]
6131                let unnest_args = if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6132                    // DuckDB: ARRAY(y) -> [y]
6133                    func_args
6134                        .iter()
6135                        .map(|a| {
6136                            if let Expression::Function(ref f) = a {
6137                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() == 1 {
6138                                    return Expression::ArrayFunc(Box::new(
6139                                        crate::expressions::ArrayConstructor {
6140                                            expressions: f.args.clone(),
6141                                            bracket_notation: true,
6142                                            use_list_keyword: false,
6143                                        },
6144                                    ));
6145                                }
6146                            }
6147                            a.clone()
6148                        })
6149                        .collect::<Vec<_>>()
6150                } else if matches!(
6151                    self.config.dialect,
6152                    Some(DialectType::Presto)
6153                        | Some(DialectType::Trino)
6154                        | Some(DialectType::Athena)
6155                ) {
6156                    // Presto: ARRAY(y) -> ARRAY[y]
6157                    func_args
6158                        .iter()
6159                        .map(|a| {
6160                            if let Expression::Function(ref f) = a {
6161                                if f.name.eq_ignore_ascii_case("ARRAY") && f.args.len() >= 1 {
6162                                    return Expression::ArrayFunc(Box::new(
6163                                        crate::expressions::ArrayConstructor {
6164                                            expressions: f.args.clone(),
6165                                            bracket_notation: true,
6166                                            use_list_keyword: false,
6167                                        },
6168                                    ));
6169                                }
6170                            }
6171                            a.clone()
6172                        })
6173                        .collect::<Vec<_>>()
6174                } else {
6175                    func_args
6176                };
6177
6178                // POSEXPLODE -> LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))
6179                if is_posexplode {
6180                    self.write_keyword("LATERAL");
6181                    self.write(" (");
6182                    self.write_keyword("SELECT");
6183                    self.write_space();
6184
6185                    // Build the outer SELECT list: pos - 1 AS pos, then data columns
6186                    // column_aliases[0] is the position column, rest are data columns
6187                    let pos_alias = if !lv.column_aliases.is_empty() {
6188                        lv.column_aliases[0].clone()
6189                    } else {
6190                        Identifier::new("pos")
6191                    };
6192                    let data_aliases: Vec<Identifier> = if lv.column_aliases.len() > 1 {
6193                        lv.column_aliases[1..].to_vec()
6194                    } else {
6195                        vec![Identifier::new("col")]
6196                    };
6197
6198                    // pos - 1 AS pos
6199                    self.generate_identifier(&pos_alias)?;
6200                    self.write(" - 1");
6201                    self.write_space();
6202                    self.write_keyword("AS");
6203                    self.write_space();
6204                    self.generate_identifier(&pos_alias)?;
6205
6206                    // , col [, key, value ...]
6207                    for data_col in &data_aliases {
6208                        self.write(", ");
6209                        self.generate_identifier(data_col)?;
6210                    }
6211
6212                    self.write_space();
6213                    self.write_keyword("FROM");
6214                    self.write_space();
6215                    self.write_keyword("UNNEST");
6216                    self.write("(");
6217                    for (i, arg) in unnest_args.iter().enumerate() {
6218                        if i > 0 {
6219                            self.write(", ");
6220                        }
6221                        self.generate_expression(arg)?;
6222                    }
6223                    self.write(")");
6224                    self.write_space();
6225                    self.write_keyword("WITH ORDINALITY");
6226                    self.write_space();
6227                    self.write_keyword("AS");
6228                    self.write_space();
6229
6230                    // Inner alias: t(data_cols..., pos) - data columns first, pos last
6231                    let table_alias_ident = lv
6232                        .table_alias
6233                        .clone()
6234                        .unwrap_or_else(|| Identifier::new("t"));
6235                    self.generate_identifier(&table_alias_ident)?;
6236                    self.write("(");
6237                    for (i, data_col) in data_aliases.iter().enumerate() {
6238                        if i > 0 {
6239                            self.write(", ");
6240                        }
6241                        self.generate_identifier(data_col)?;
6242                    }
6243                    self.write(", ");
6244                    self.generate_identifier(&pos_alias)?;
6245                    self.write("))");
6246                } else if is_inline && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
6247                    // INLINE -> LATERAL (SELECT UNNEST(arg, max_depth => 2)) AS alias
6248                    self.write_keyword("LATERAL");
6249                    self.write(" (");
6250                    self.write_keyword("SELECT");
6251                    self.write_space();
6252                    self.write_keyword("UNNEST");
6253                    self.write("(");
6254                    for (i, arg) in unnest_args.iter().enumerate() {
6255                        if i > 0 {
6256                            self.write(", ");
6257                        }
6258                        self.generate_expression(arg)?;
6259                    }
6260                    self.write(", ");
6261                    self.write_keyword("max_depth");
6262                    self.write(" => 2))");
6263
6264                    // Add table and column aliases
6265                    if let Some(alias) = &lv.table_alias {
6266                        self.write_space();
6267                        self.write_keyword("AS");
6268                        self.write_space();
6269                        self.generate_identifier(alias)?;
6270                        if !lv.column_aliases.is_empty() {
6271                            self.write("(");
6272                            for (i, col) in lv.column_aliases.iter().enumerate() {
6273                                if i > 0 {
6274                                    self.write(", ");
6275                                }
6276                                self.generate_identifier(col)?;
6277                            }
6278                            self.write(")");
6279                        }
6280                    } else if !lv.column_aliases.is_empty() {
6281                        // Auto-generate alias like _u_N
6282                        self.write_space();
6283                        self.write_keyword("AS");
6284                        self.write_space();
6285                        self.write(&format!("_u_{}", lv_index));
6286                        self.write("(");
6287                        for (i, col) in lv.column_aliases.iter().enumerate() {
6288                            if i > 0 {
6289                                self.write(", ");
6290                            }
6291                            self.generate_identifier(col)?;
6292                        }
6293                        self.write(")");
6294                    }
6295                } else {
6296                    self.write_keyword("UNNEST");
6297                    self.write("(");
6298                    for (i, arg) in unnest_args.iter().enumerate() {
6299                        if i > 0 {
6300                            self.write(", ");
6301                        }
6302                        self.generate_expression(arg)?;
6303                    }
6304                    self.write(")");
6305
6306                    // Add table and column aliases for non-POSEXPLODE
6307                    if let Some(alias) = &lv.table_alias {
6308                        self.write_space();
6309                        self.write_keyword("AS");
6310                        self.write_space();
6311                        self.generate_identifier(alias)?;
6312                        if !lv.column_aliases.is_empty() {
6313                            self.write("(");
6314                            for (i, col) in lv.column_aliases.iter().enumerate() {
6315                                if i > 0 {
6316                                    self.write(", ");
6317                                }
6318                                self.generate_identifier(col)?;
6319                            }
6320                            self.write(")");
6321                        }
6322                    } else if !lv.column_aliases.is_empty() {
6323                        self.write_space();
6324                        self.write_keyword("AS");
6325                        self.write(" t(");
6326                        for (i, col) in lv.column_aliases.iter().enumerate() {
6327                            if i > 0 {
6328                                self.write(", ");
6329                            }
6330                            self.generate_identifier(col)?;
6331                        }
6332                        self.write(")");
6333                    }
6334                }
6335            } else {
6336                // Not EXPLODE/POSEXPLODE or not using UNNEST, use LATERAL
6337                if !lv.outer {
6338                    self.write_keyword("LATERAL");
6339                    self.write_space();
6340                }
6341                self.generate_expression(&lv.this)?;
6342
6343                // Add table and column aliases
6344                if let Some(alias) = &lv.table_alias {
6345                    self.write_space();
6346                    self.write_keyword("AS");
6347                    self.write_space();
6348                    self.generate_identifier(alias)?;
6349                    if !lv.column_aliases.is_empty() {
6350                        self.write("(");
6351                        for (i, col) in lv.column_aliases.iter().enumerate() {
6352                            if i > 0 {
6353                                self.write(", ");
6354                            }
6355                            self.generate_identifier(col)?;
6356                        }
6357                        self.write(")");
6358                    }
6359                } else if !lv.column_aliases.is_empty() {
6360                    self.write_space();
6361                    self.write_keyword("AS");
6362                    self.write(" t(");
6363                    for (i, col) in lv.column_aliases.iter().enumerate() {
6364                        if i > 0 {
6365                            self.write(", ");
6366                        }
6367                        self.generate_identifier(col)?;
6368                    }
6369                    self.write(")");
6370                }
6371            }
6372
6373            // For LEFT JOIN LATERAL, need ON TRUE
6374            if lv.outer {
6375                self.write_space();
6376                self.write_keyword("ON TRUE");
6377            }
6378        } else {
6379            // Output native LATERAL VIEW syntax (Hive/Spark/Databricks or default)
6380            self.write_keyword("LATERAL VIEW");
6381            if lv.outer {
6382                self.write_space();
6383                self.write_keyword("OUTER");
6384            }
6385            if self.config.pretty {
6386                self.write_newline();
6387                self.write_indent();
6388            } else {
6389                self.write_space();
6390            }
6391            self.generate_expression(&lv.this)?;
6392
6393            // Table alias
6394            if let Some(alias) = &lv.table_alias {
6395                self.write_space();
6396                self.generate_identifier(alias)?;
6397            }
6398
6399            // Column aliases
6400            if !lv.column_aliases.is_empty() {
6401                self.write_space();
6402                self.write_keyword("AS");
6403                self.write_space();
6404                for (i, col) in lv.column_aliases.iter().enumerate() {
6405                    if i > 0 {
6406                        self.write(", ");
6407                    }
6408                    self.generate_identifier(col)?;
6409                }
6410            }
6411        }
6412
6413        Ok(())
6414    }
6415
6416    fn should_wrap_set_operation_modifiers(
6417        &self,
6418        order_by: &Option<OrderBy>,
6419        limit: &Option<Box<Expression>>,
6420        offset: &Option<Box<Expression>>,
6421    ) -> bool {
6422        let has_row_limit = limit.is_some() || offset.is_some();
6423        let has_emulated_null_ordering = order_by.as_ref().map_or(false, |order_by| {
6424            order_by
6425                .expressions
6426                .iter()
6427                .any(|ordered| ordered.nulls_first.is_some())
6428        });
6429
6430        (has_row_limit || has_emulated_null_ordering)
6431            && matches!(
6432                self.config.dialect,
6433                Some(DialectType::TSQL) | Some(DialectType::Fabric)
6434            )
6435    }
6436
6437    fn generate_tsql_wrapped_set_operation(
6438        &mut self,
6439        inner: Expression,
6440        with: Option<With>,
6441        order_by: Option<OrderBy>,
6442        limit: Option<Box<Expression>>,
6443        offset: Option<Box<Expression>>,
6444    ) -> Result<()> {
6445        let subquery = Subquery {
6446            this: inner,
6447            alias: Some(Identifier::new("_l_0".to_string())),
6448            column_aliases: Vec::new(),
6449            alias_explicit_as: true,
6450            alias_keyword: None,
6451            order_by: None,
6452            limit: None,
6453            offset: None,
6454            lateral: false,
6455            modifiers_inside: false,
6456            trailing_comments: Vec::new(),
6457            distribute_by: None,
6458            sort_by: None,
6459            cluster_by: None,
6460            inferred_type: None,
6461        };
6462
6463        let mut outer_select = Select {
6464            expressions: vec![Expression::Star(Star {
6465                table: None,
6466                except: None,
6467                replace: None,
6468                rename: None,
6469                trailing_comments: Vec::new(),
6470                span: None,
6471            })],
6472            from: Some(From {
6473                expressions: vec![Expression::Subquery(Box::new(subquery))],
6474            }),
6475            with,
6476            order_by,
6477            limit: limit.map(|limit| Limit {
6478                this: *limit,
6479                percent: false,
6480                comments: Vec::new(),
6481            }),
6482            offset: offset.map(|offset| Offset {
6483                this: *offset,
6484                rows: Some(true),
6485            }),
6486            ..Select::new()
6487        };
6488
6489        if outer_select.offset.is_some() && outer_select.order_by.is_none() {
6490            outer_select.order_by = Some(Self::dummy_tsql_order_by());
6491        }
6492
6493        self.generate_select(&outer_select)
6494    }
6495
6496    fn dummy_tsql_order_by() -> OrderBy {
6497        let null_select = Expression::Select(Box::new(Select {
6498            expressions: vec![Expression::Null(Null)],
6499            ..Select::new()
6500        }));
6501
6502        OrderBy {
6503            expressions: vec![Ordered {
6504                this: Expression::Subquery(Box::new(Subquery {
6505                    this: null_select,
6506                    alias: None,
6507                    column_aliases: Vec::new(),
6508                    alias_explicit_as: false,
6509                    alias_keyword: None,
6510                    order_by: None,
6511                    limit: None,
6512                    offset: None,
6513                    lateral: false,
6514                    modifiers_inside: false,
6515                    trailing_comments: Vec::new(),
6516                    distribute_by: None,
6517                    sort_by: None,
6518                    cluster_by: None,
6519                    inferred_type: None,
6520                })),
6521                desc: false,
6522                nulls_first: None,
6523                explicit_asc: false,
6524                with_fill: None,
6525            }],
6526            siblings: false,
6527            comments: Vec::new(),
6528        }
6529    }
6530
6531    fn generate_union(&mut self, outermost: &Union) -> Result<()> {
6532        if self.should_wrap_set_operation_modifiers(
6533            &outermost.order_by,
6534            &outermost.limit,
6535            &outermost.offset,
6536        ) {
6537            let mut inner = outermost.clone();
6538            let with = inner.with.take();
6539            let order_by = inner.order_by.take();
6540            let limit = inner.limit.take();
6541            let offset = inner.offset.take();
6542
6543            return self.generate_tsql_wrapped_set_operation(
6544                Expression::Union(Box::new(inner)),
6545                with,
6546                order_by,
6547                limit,
6548                offset,
6549            );
6550        }
6551
6552        // Collect the left-recursive chain of Union nodes iteratively.
6553        // This avoids stack overflow for deeply nested chains like
6554        // SELECT 1 UNION ALL SELECT 2 UNION ALL ... UNION ALL SELECT N
6555        // where the parser builds: Union(Union(Union(A, B), C), D)
6556        let mut chain: Vec<&Union> = vec![outermost];
6557        let mut leftmost: &Expression = &outermost.left;
6558        while let Expression::Union(inner) = leftmost {
6559            chain.push(inner);
6560            leftmost = &inner.left;
6561        }
6562        // chain[0] = outermost, chain[last] = innermost
6563        // leftmost = innermost.left (a non-Union expression, typically Select)
6564
6565        // WITH clause (only on outermost)
6566        if let Some(with) = &outermost.with {
6567            self.generate_with(with)?;
6568            self.write_space();
6569        }
6570
6571        // Generate the base (leftmost) expression
6572        self.generate_expression(leftmost)?;
6573
6574        // Generate each union step from innermost to outermost
6575        for union in chain.iter().rev() {
6576            self.generate_union_step(union)?;
6577        }
6578        Ok(())
6579    }
6580
6581    /// Generate a single UNION step: keyword, right expression, and trailing modifiers.
6582    fn generate_union_step(&mut self, union: &Union) -> Result<()> {
6583        if self.config.pretty {
6584            self.write_newline();
6585            self.write_indent();
6586        } else {
6587            self.write_space();
6588        }
6589
6590        // BigQuery set operation modifiers: [side] [kind] UNION
6591        if let Some(side) = &union.side {
6592            self.write_keyword(side);
6593            self.write_space();
6594        }
6595        if let Some(kind) = &union.kind {
6596            self.write_keyword(kind);
6597            self.write_space();
6598        }
6599
6600        self.write_keyword("UNION");
6601        if union.all {
6602            self.write_space();
6603            self.write_keyword("ALL");
6604        } else if union.distinct {
6605            self.write_space();
6606            self.write_keyword("DISTINCT");
6607        }
6608
6609        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6610        // DuckDB: BY NAME
6611        if union.corresponding || union.by_name {
6612            self.write_space();
6613            self.write_keyword("BY NAME");
6614        }
6615        if !union.on_columns.is_empty() {
6616            self.write_space();
6617            self.write_keyword("ON");
6618            self.write(" (");
6619            for (i, col) in union.on_columns.iter().enumerate() {
6620                if i > 0 {
6621                    self.write(", ");
6622                }
6623                self.generate_expression(col)?;
6624            }
6625            self.write(")");
6626        }
6627
6628        if self.config.pretty {
6629            self.write_newline();
6630            self.write_indent();
6631        } else {
6632            self.write_space();
6633        }
6634        self.generate_expression(&union.right)?;
6635        // ORDER BY, LIMIT, OFFSET for the set operation
6636        if let Some(order_by) = &union.order_by {
6637            if self.config.pretty {
6638                self.write_newline();
6639            } else {
6640                self.write_space();
6641            }
6642            self.write_keyword("ORDER BY");
6643            self.write_space();
6644            for (i, ordered) in order_by.expressions.iter().enumerate() {
6645                if i > 0 {
6646                    self.write(", ");
6647                }
6648                self.generate_ordered(ordered)?;
6649            }
6650        }
6651        if let Some(limit) = &union.limit {
6652            if self.config.pretty {
6653                self.write_newline();
6654            } else {
6655                self.write_space();
6656            }
6657            self.write_keyword("LIMIT");
6658            self.write_space();
6659            self.generate_expression(limit)?;
6660        }
6661        if let Some(offset) = &union.offset {
6662            if self.config.pretty {
6663                self.write_newline();
6664            } else {
6665                self.write_space();
6666            }
6667            self.write_keyword("OFFSET");
6668            self.write_space();
6669            self.generate_expression(offset)?;
6670        }
6671        // DISTRIBUTE BY (Hive/Spark)
6672        if let Some(distribute_by) = &union.distribute_by {
6673            self.write_space();
6674            self.write_keyword("DISTRIBUTE BY");
6675            self.write_space();
6676            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6677                if i > 0 {
6678                    self.write(", ");
6679                }
6680                self.generate_expression(expr)?;
6681            }
6682        }
6683        // SORT BY (Hive/Spark)
6684        if let Some(sort_by) = &union.sort_by {
6685            self.write_space();
6686            self.write_keyword("SORT BY");
6687            self.write_space();
6688            for (i, ord) in sort_by.expressions.iter().enumerate() {
6689                if i > 0 {
6690                    self.write(", ");
6691                }
6692                self.generate_ordered(ord)?;
6693            }
6694        }
6695        // CLUSTER BY (Hive/Spark)
6696        if let Some(cluster_by) = &union.cluster_by {
6697            self.write_space();
6698            self.write_keyword("CLUSTER BY");
6699            self.write_space();
6700            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6701                if i > 0 {
6702                    self.write(", ");
6703                }
6704                self.generate_ordered(ord)?;
6705            }
6706        }
6707        Ok(())
6708    }
6709
6710    fn generate_intersect(&mut self, outermost: &Intersect) -> Result<()> {
6711        if self.should_wrap_set_operation_modifiers(
6712            &outermost.order_by,
6713            &outermost.limit,
6714            &outermost.offset,
6715        ) {
6716            let mut inner = outermost.clone();
6717            let with = inner.with.take();
6718            let order_by = inner.order_by.take();
6719            let limit = inner.limit.take();
6720            let offset = inner.offset.take();
6721
6722            return self.generate_tsql_wrapped_set_operation(
6723                Expression::Intersect(Box::new(inner)),
6724                with,
6725                order_by,
6726                limit,
6727                offset,
6728            );
6729        }
6730
6731        // Collect the left-recursive chain iteratively to avoid stack overflow
6732        let mut chain: Vec<&Intersect> = vec![outermost];
6733        let mut leftmost: &Expression = &outermost.left;
6734        while let Expression::Intersect(inner) = leftmost {
6735            chain.push(inner);
6736            leftmost = &inner.left;
6737        }
6738
6739        if let Some(with) = &outermost.with {
6740            self.generate_with(with)?;
6741            self.write_space();
6742        }
6743
6744        self.generate_expression(leftmost)?;
6745
6746        for intersect in chain.iter().rev() {
6747            self.generate_intersect_step(intersect)?;
6748        }
6749        Ok(())
6750    }
6751
6752    /// Generate a single INTERSECT step: keyword, right expression, and trailing modifiers.
6753    fn generate_intersect_step(&mut self, intersect: &Intersect) -> Result<()> {
6754        if self.config.pretty {
6755            self.write_newline();
6756            self.write_indent();
6757        } else {
6758            self.write_space();
6759        }
6760
6761        // BigQuery set operation modifiers: [side] [kind] INTERSECT
6762        if let Some(side) = &intersect.side {
6763            self.write_keyword(side);
6764            self.write_space();
6765        }
6766        if let Some(kind) = &intersect.kind {
6767            self.write_keyword(kind);
6768            self.write_space();
6769        }
6770
6771        self.write_keyword("INTERSECT");
6772        if intersect.all {
6773            self.write_space();
6774            self.write_keyword("ALL");
6775        } else if intersect.distinct {
6776            self.write_space();
6777            self.write_keyword("DISTINCT");
6778        }
6779
6780        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6781        // DuckDB: BY NAME
6782        if intersect.corresponding || intersect.by_name {
6783            self.write_space();
6784            self.write_keyword("BY NAME");
6785        }
6786        if !intersect.on_columns.is_empty() {
6787            self.write_space();
6788            self.write_keyword("ON");
6789            self.write(" (");
6790            for (i, col) in intersect.on_columns.iter().enumerate() {
6791                if i > 0 {
6792                    self.write(", ");
6793                }
6794                self.generate_expression(col)?;
6795            }
6796            self.write(")");
6797        }
6798
6799        if self.config.pretty {
6800            self.write_newline();
6801            self.write_indent();
6802        } else {
6803            self.write_space();
6804        }
6805        self.generate_expression(&intersect.right)?;
6806        // ORDER BY, LIMIT, OFFSET for the set operation
6807        if let Some(order_by) = &intersect.order_by {
6808            if self.config.pretty {
6809                self.write_newline();
6810            } else {
6811                self.write_space();
6812            }
6813            self.write_keyword("ORDER BY");
6814            self.write_space();
6815            for (i, ordered) in order_by.expressions.iter().enumerate() {
6816                if i > 0 {
6817                    self.write(", ");
6818                }
6819                self.generate_ordered(ordered)?;
6820            }
6821        }
6822        if let Some(limit) = &intersect.limit {
6823            if self.config.pretty {
6824                self.write_newline();
6825            } else {
6826                self.write_space();
6827            }
6828            self.write_keyword("LIMIT");
6829            self.write_space();
6830            self.generate_expression(limit)?;
6831        }
6832        if let Some(offset) = &intersect.offset {
6833            if self.config.pretty {
6834                self.write_newline();
6835            } else {
6836                self.write_space();
6837            }
6838            self.write_keyword("OFFSET");
6839            self.write_space();
6840            self.generate_expression(offset)?;
6841        }
6842        // DISTRIBUTE BY (Hive/Spark)
6843        if let Some(distribute_by) = &intersect.distribute_by {
6844            self.write_space();
6845            self.write_keyword("DISTRIBUTE BY");
6846            self.write_space();
6847            for (i, expr) in distribute_by.expressions.iter().enumerate() {
6848                if i > 0 {
6849                    self.write(", ");
6850                }
6851                self.generate_expression(expr)?;
6852            }
6853        }
6854        // SORT BY (Hive/Spark)
6855        if let Some(sort_by) = &intersect.sort_by {
6856            self.write_space();
6857            self.write_keyword("SORT BY");
6858            self.write_space();
6859            for (i, ord) in sort_by.expressions.iter().enumerate() {
6860                if i > 0 {
6861                    self.write(", ");
6862                }
6863                self.generate_ordered(ord)?;
6864            }
6865        }
6866        // CLUSTER BY (Hive/Spark)
6867        if let Some(cluster_by) = &intersect.cluster_by {
6868            self.write_space();
6869            self.write_keyword("CLUSTER BY");
6870            self.write_space();
6871            for (i, ord) in cluster_by.expressions.iter().enumerate() {
6872                if i > 0 {
6873                    self.write(", ");
6874                }
6875                self.generate_ordered(ord)?;
6876            }
6877        }
6878        Ok(())
6879    }
6880
6881    fn generate_except(&mut self, outermost: &Except) -> Result<()> {
6882        if self.should_wrap_set_operation_modifiers(
6883            &outermost.order_by,
6884            &outermost.limit,
6885            &outermost.offset,
6886        ) {
6887            let mut inner = outermost.clone();
6888            let with = inner.with.take();
6889            let order_by = inner.order_by.take();
6890            let limit = inner.limit.take();
6891            let offset = inner.offset.take();
6892
6893            return self.generate_tsql_wrapped_set_operation(
6894                Expression::Except(Box::new(inner)),
6895                with,
6896                order_by,
6897                limit,
6898                offset,
6899            );
6900        }
6901
6902        // Collect the left-recursive chain iteratively to avoid stack overflow
6903        let mut chain: Vec<&Except> = vec![outermost];
6904        let mut leftmost: &Expression = &outermost.left;
6905        while let Expression::Except(inner) = leftmost {
6906            chain.push(inner);
6907            leftmost = &inner.left;
6908        }
6909
6910        if let Some(with) = &outermost.with {
6911            self.generate_with(with)?;
6912            self.write_space();
6913        }
6914
6915        self.generate_expression(leftmost)?;
6916
6917        for except in chain.iter().rev() {
6918            self.generate_except_step(except)?;
6919        }
6920        Ok(())
6921    }
6922
6923    /// Generate a single EXCEPT step: keyword, right expression, and trailing modifiers.
6924    fn generate_except_step(&mut self, except: &Except) -> Result<()> {
6925        use crate::dialects::DialectType;
6926
6927        if self.config.pretty {
6928            self.write_newline();
6929            self.write_indent();
6930        } else {
6931            self.write_space();
6932        }
6933
6934        // BigQuery set operation modifiers: [side] [kind] EXCEPT
6935        if let Some(side) = &except.side {
6936            self.write_keyword(side);
6937            self.write_space();
6938        }
6939        if let Some(kind) = &except.kind {
6940            self.write_keyword(kind);
6941            self.write_space();
6942        }
6943
6944        // Oracle uses MINUS instead of EXCEPT (but not for EXCEPT ALL)
6945        match self.config.dialect {
6946            Some(DialectType::Oracle) if !except.all => {
6947                self.write_keyword("MINUS");
6948            }
6949            Some(DialectType::ClickHouse) => {
6950                self.write_keyword("EXCEPT");
6951                let preserve_all = self.config.source_dialect.is_none()
6952                    || matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
6953                if except.all && preserve_all {
6954                    self.write_space();
6955                    self.write_keyword("ALL");
6956                }
6957                if except.distinct {
6958                    self.write_space();
6959                    self.write_keyword("DISTINCT");
6960                }
6961            }
6962            Some(DialectType::BigQuery) => {
6963                // BigQuery: bare EXCEPT defaults to EXCEPT DISTINCT
6964                self.write_keyword("EXCEPT");
6965                if except.all {
6966                    self.write_space();
6967                    self.write_keyword("ALL");
6968                } else {
6969                    self.write_space();
6970                    self.write_keyword("DISTINCT");
6971                }
6972            }
6973            _ => {
6974                self.write_keyword("EXCEPT");
6975                if except.all {
6976                    self.write_space();
6977                    self.write_keyword("ALL");
6978                } else if except.distinct {
6979                    self.write_space();
6980                    self.write_keyword("DISTINCT");
6981                }
6982            }
6983        }
6984
6985        // BigQuery: CORRESPONDING/STRICT CORRESPONDING -> BY NAME, BY (cols) -> ON (cols)
6986        // DuckDB: BY NAME
6987        if except.corresponding || except.by_name {
6988            self.write_space();
6989            self.write_keyword("BY NAME");
6990        }
6991        if !except.on_columns.is_empty() {
6992            self.write_space();
6993            self.write_keyword("ON");
6994            self.write(" (");
6995            for (i, col) in except.on_columns.iter().enumerate() {
6996                if i > 0 {
6997                    self.write(", ");
6998                }
6999                self.generate_expression(col)?;
7000            }
7001            self.write(")");
7002        }
7003
7004        if self.config.pretty {
7005            self.write_newline();
7006            self.write_indent();
7007        } else {
7008            self.write_space();
7009        }
7010        self.generate_expression(&except.right)?;
7011        // ORDER BY, LIMIT, OFFSET for the set operation
7012        if let Some(order_by) = &except.order_by {
7013            if self.config.pretty {
7014                self.write_newline();
7015            } else {
7016                self.write_space();
7017            }
7018            self.write_keyword("ORDER BY");
7019            self.write_space();
7020            for (i, ordered) in order_by.expressions.iter().enumerate() {
7021                if i > 0 {
7022                    self.write(", ");
7023                }
7024                self.generate_ordered(ordered)?;
7025            }
7026        }
7027        if let Some(limit) = &except.limit {
7028            if self.config.pretty {
7029                self.write_newline();
7030            } else {
7031                self.write_space();
7032            }
7033            self.write_keyword("LIMIT");
7034            self.write_space();
7035            self.generate_expression(limit)?;
7036        }
7037        if let Some(offset) = &except.offset {
7038            if self.config.pretty {
7039                self.write_newline();
7040            } else {
7041                self.write_space();
7042            }
7043            self.write_keyword("OFFSET");
7044            self.write_space();
7045            self.generate_expression(offset)?;
7046        }
7047        // DISTRIBUTE BY (Hive/Spark)
7048        if let Some(distribute_by) = &except.distribute_by {
7049            self.write_space();
7050            self.write_keyword("DISTRIBUTE BY");
7051            self.write_space();
7052            for (i, expr) in distribute_by.expressions.iter().enumerate() {
7053                if i > 0 {
7054                    self.write(", ");
7055                }
7056                self.generate_expression(expr)?;
7057            }
7058        }
7059        // SORT BY (Hive/Spark)
7060        if let Some(sort_by) = &except.sort_by {
7061            self.write_space();
7062            self.write_keyword("SORT BY");
7063            self.write_space();
7064            for (i, ord) in sort_by.expressions.iter().enumerate() {
7065                if i > 0 {
7066                    self.write(", ");
7067                }
7068                self.generate_ordered(ord)?;
7069            }
7070        }
7071        // CLUSTER BY (Hive/Spark)
7072        if let Some(cluster_by) = &except.cluster_by {
7073            self.write_space();
7074            self.write_keyword("CLUSTER BY");
7075            self.write_space();
7076            for (i, ord) in cluster_by.expressions.iter().enumerate() {
7077                if i > 0 {
7078                    self.write(", ");
7079                }
7080                self.generate_ordered(ord)?;
7081            }
7082        }
7083        Ok(())
7084    }
7085
7086    fn generate_insert(&mut self, insert: &Insert) -> Result<()> {
7087        // For TSQL/Fabric/Spark/Hive/Databricks, CTEs must be prepended before INSERT
7088        let prepend_query_cte = if insert.with.is_none() {
7089            use crate::dialects::DialectType;
7090            let should_prepend = matches!(
7091                self.config.dialect,
7092                Some(DialectType::TSQL)
7093                    | Some(DialectType::Fabric)
7094                    | Some(DialectType::Spark)
7095                    | Some(DialectType::Databricks)
7096                    | Some(DialectType::Hive)
7097            );
7098            if should_prepend {
7099                if let Some(Expression::Select(select)) = &insert.query {
7100                    select.with.clone()
7101                } else {
7102                    None
7103                }
7104            } else {
7105                None
7106            }
7107        } else {
7108            None
7109        };
7110
7111        // Output WITH clause if on INSERT (e.g., WITH ... INSERT INTO ...)
7112        if let Some(with) = &insert.with {
7113            self.generate_with(with)?;
7114            self.write_space();
7115        } else if let Some(with) = &prepend_query_cte {
7116            self.generate_with(with)?;
7117            self.write_space();
7118        }
7119
7120        // Output leading comments before INSERT
7121        for comment in &insert.leading_comments {
7122            self.write_formatted_comment(comment);
7123            self.write(" ");
7124        }
7125
7126        // Handle directory insert (INSERT OVERWRITE DIRECTORY)
7127        if let Some(dir) = &insert.directory {
7128            self.write_keyword("INSERT OVERWRITE");
7129            if dir.local {
7130                self.write_space();
7131                self.write_keyword("LOCAL");
7132            }
7133            self.write_space();
7134            self.write_keyword("DIRECTORY");
7135            self.write_space();
7136            self.write("'");
7137            self.write(&dir.path);
7138            self.write("'");
7139
7140            // ROW FORMAT clause
7141            if let Some(row_format) = &dir.row_format {
7142                self.write_space();
7143                self.write_keyword("ROW FORMAT");
7144                if row_format.delimited {
7145                    self.write_space();
7146                    self.write_keyword("DELIMITED");
7147                }
7148                if let Some(val) = &row_format.fields_terminated_by {
7149                    self.write_space();
7150                    self.write_keyword("FIELDS TERMINATED BY");
7151                    self.write_space();
7152                    self.generate_string_literal(val)?;
7153                }
7154                if let Some(val) = &row_format.collection_items_terminated_by {
7155                    self.write_space();
7156                    self.write_keyword("COLLECTION ITEMS TERMINATED BY");
7157                    self.write_space();
7158                    self.write("'");
7159                    self.write(val);
7160                    self.write("'");
7161                }
7162                if let Some(val) = &row_format.map_keys_terminated_by {
7163                    self.write_space();
7164                    self.write_keyword("MAP KEYS TERMINATED BY");
7165                    self.write_space();
7166                    self.write("'");
7167                    self.write(val);
7168                    self.write("'");
7169                }
7170                if let Some(val) = &row_format.lines_terminated_by {
7171                    self.write_space();
7172                    self.write_keyword("LINES TERMINATED BY");
7173                    self.write_space();
7174                    self.write("'");
7175                    self.write(val);
7176                    self.write("'");
7177                }
7178                if let Some(val) = &row_format.null_defined_as {
7179                    self.write_space();
7180                    self.write_keyword("NULL DEFINED AS");
7181                    self.write_space();
7182                    self.write("'");
7183                    self.write(val);
7184                    self.write("'");
7185                }
7186            }
7187
7188            // STORED AS clause
7189            if let Some(format) = &dir.stored_as {
7190                self.write_space();
7191                self.write_keyword("STORED AS");
7192                self.write_space();
7193                self.write_keyword(format);
7194            }
7195
7196            // Query (SELECT statement)
7197            if let Some(query) = &insert.query {
7198                self.write_space();
7199                self.generate_expression(query)?;
7200            }
7201
7202            return Ok(());
7203        }
7204
7205        if insert.is_replace {
7206            // MySQL/SQLite REPLACE INTO statement
7207            self.write_keyword("REPLACE INTO");
7208        } else if insert.overwrite {
7209            // Use dialect-specific INSERT OVERWRITE format
7210            self.write_keyword("INSERT");
7211            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7212            if let Some(ref hint) = insert.hint {
7213                self.generate_hint(hint)?;
7214            }
7215            self.write(&self.config.insert_overwrite.to_ascii_uppercase());
7216        } else if let Some(ref action) = insert.conflict_action {
7217            // SQLite conflict action: INSERT OR ABORT|FAIL|IGNORE|REPLACE|ROLLBACK INTO
7218            self.write_keyword("INSERT OR");
7219            self.write_space();
7220            self.write_keyword(action);
7221            self.write_space();
7222            self.write_keyword("INTO");
7223        } else if insert.ignore {
7224            // MySQL INSERT IGNORE syntax
7225            self.write_keyword("INSERT IGNORE INTO");
7226        } else {
7227            self.write_keyword("INSERT");
7228            // Output hint if present (Oracle: INSERT /*+ APPEND */ INTO)
7229            if let Some(ref hint) = insert.hint {
7230                self.generate_hint(hint)?;
7231            }
7232            self.write_space();
7233            self.write_keyword("INTO");
7234        }
7235        // ClickHouse: INSERT INTO FUNCTION func_name(args...)
7236        if let Some(ref func) = insert.function_target {
7237            self.write_space();
7238            self.write_keyword("FUNCTION");
7239            self.write_space();
7240            self.generate_expression(func)?;
7241        } else {
7242            self.write_space();
7243            self.generate_table(&insert.table)?;
7244        }
7245
7246        // Table alias (PostgreSQL: INSERT INTO table AS t(...), Oracle: INSERT INTO table t ...)
7247        if let Some(ref alias) = insert.alias {
7248            self.write_space();
7249            if insert.alias_explicit_as {
7250                self.write_keyword("AS");
7251                self.write_space();
7252            }
7253            self.generate_identifier(alias)?;
7254        }
7255
7256        // IF EXISTS clause (Hive)
7257        if insert.if_exists {
7258            self.write_space();
7259            self.write_keyword("IF EXISTS");
7260        }
7261
7262        // REPLACE WHERE clause (Databricks)
7263        if let Some(ref replace_where) = insert.replace_where {
7264            if self.config.pretty {
7265                self.write_newline();
7266                self.write_indent();
7267            } else {
7268                self.write_space();
7269            }
7270            self.write_keyword("REPLACE WHERE");
7271            self.write_space();
7272            self.generate_expression(replace_where)?;
7273        }
7274
7275        // Generate PARTITION clause if present
7276        if !insert.partition.is_empty() {
7277            self.write_space();
7278            self.write_keyword("PARTITION");
7279            self.write("(");
7280            for (i, (col, val)) in insert.partition.iter().enumerate() {
7281                if i > 0 {
7282                    self.write(", ");
7283                }
7284                self.generate_identifier(col)?;
7285                if let Some(v) = val {
7286                    self.write(" = ");
7287                    self.generate_expression(v)?;
7288                }
7289            }
7290            self.write(")");
7291        }
7292
7293        // ClickHouse: PARTITION BY expr
7294        if let Some(ref partition_by) = insert.partition_by {
7295            self.write_space();
7296            self.write_keyword("PARTITION BY");
7297            self.write_space();
7298            self.generate_expression(partition_by)?;
7299        }
7300
7301        // ClickHouse: SETTINGS key = val, ...
7302        if !insert.settings.is_empty() {
7303            self.write_space();
7304            self.write_keyword("SETTINGS");
7305            self.write_space();
7306            for (i, setting) in insert.settings.iter().enumerate() {
7307                if i > 0 {
7308                    self.write(", ");
7309                }
7310                self.generate_expression(setting)?;
7311            }
7312        }
7313
7314        if !insert.columns.is_empty() {
7315            if insert.alias.is_some() && insert.alias_explicit_as {
7316                // No space when explicit AS alias is present: INSERT INTO table AS t(a, b, c)
7317                self.write("(");
7318            } else {
7319                // Space for implicit alias or no alias: INSERT INTO dest d (i, value)
7320                self.write(" (");
7321            }
7322            for (i, col) in insert.columns.iter().enumerate() {
7323                if i > 0 {
7324                    self.write(", ");
7325                }
7326                self.generate_identifier(col)?;
7327            }
7328            self.write(")");
7329        }
7330
7331        // OUTPUT clause (TSQL)
7332        if let Some(ref output) = insert.output {
7333            self.generate_output_clause(output)?;
7334        }
7335
7336        // BY NAME modifier (DuckDB)
7337        if insert.by_name {
7338            self.write_space();
7339            self.write_keyword("BY NAME");
7340        }
7341
7342        if insert.default_values {
7343            self.write_space();
7344            self.write_keyword("DEFAULT VALUES");
7345        } else if let Some(query) = &insert.query {
7346            if self.config.pretty {
7347                self.write_newline();
7348            } else {
7349                self.write_space();
7350            }
7351            // If we prepended CTEs from nested SELECT (TSQL), strip the WITH from SELECT
7352            if prepend_query_cte.is_some() {
7353                if let Expression::Select(select) = query {
7354                    let mut select_no_with = select.clone();
7355                    select_no_with.with = None;
7356                    self.generate_select(&select_no_with)?;
7357                } else {
7358                    self.generate_expression(query)?;
7359                }
7360            } else {
7361                self.generate_expression(query)?;
7362            }
7363        } else if !insert.values.is_empty() {
7364            if self.config.pretty {
7365                // Pretty printing: VALUES on new line, each tuple indented
7366                self.write_newline();
7367                self.write_keyword("VALUES");
7368                self.write_newline();
7369                self.indent_level += 1;
7370                for (i, row) in insert.values.iter().enumerate() {
7371                    if i > 0 {
7372                        self.write(",");
7373                        self.write_newline();
7374                    }
7375                    self.write_indent();
7376                    self.write("(");
7377                    for (j, val) in row.iter().enumerate() {
7378                        if j > 0 {
7379                            self.write(", ");
7380                        }
7381                        self.generate_expression(val)?;
7382                    }
7383                    self.write(")");
7384                }
7385                self.indent_level -= 1;
7386            } else {
7387                // Non-pretty: single line
7388                self.write_space();
7389                self.write_keyword("VALUES");
7390                for (i, row) in insert.values.iter().enumerate() {
7391                    if i > 0 {
7392                        self.write(",");
7393                    }
7394                    self.write(" (");
7395                    for (j, val) in row.iter().enumerate() {
7396                        if j > 0 {
7397                            self.write(", ");
7398                        }
7399                        self.generate_expression(val)?;
7400                    }
7401                    self.write(")");
7402                }
7403            }
7404        }
7405
7406        // Source table (Hive/Spark): INSERT OVERWRITE TABLE target TABLE source
7407        if let Some(ref source) = insert.source {
7408            self.write_space();
7409            self.write_keyword("TABLE");
7410            self.write_space();
7411            self.generate_expression(source)?;
7412        }
7413
7414        // Source alias (MySQL: VALUES (...) AS new_data)
7415        if let Some(alias) = &insert.source_alias {
7416            self.write_space();
7417            self.write_keyword("AS");
7418            self.write_space();
7419            self.generate_identifier(alias)?;
7420        }
7421
7422        // ON CONFLICT clause (Materialize doesn't support ON CONFLICT)
7423        if let Some(on_conflict) = &insert.on_conflict {
7424            if !matches!(self.config.dialect, Some(DialectType::Materialize)) {
7425                self.write_space();
7426                self.generate_expression(on_conflict)?;
7427            }
7428        }
7429
7430        // RETURNING clause
7431        if !insert.returning.is_empty() {
7432            self.write_space();
7433            self.write_keyword("RETURNING");
7434            self.write_space();
7435            for (i, expr) in insert.returning.iter().enumerate() {
7436                if i > 0 {
7437                    self.write(", ");
7438                }
7439                self.generate_expression(expr)?;
7440            }
7441        }
7442
7443        Ok(())
7444    }
7445
7446    fn generate_update(&mut self, update: &Update) -> Result<()> {
7447        // Output leading comments before UPDATE
7448        for comment in &update.leading_comments {
7449            self.write_formatted_comment(comment);
7450            self.write(" ");
7451        }
7452
7453        // WITH clause (CTEs)
7454        if let Some(ref with) = update.with {
7455            self.generate_with(with)?;
7456            self.write_space();
7457        }
7458
7459        self.write_keyword("UPDATE");
7460        if let Some(hint) = &update.hint {
7461            self.generate_hint(hint)?;
7462        }
7463        self.write_space();
7464        self.generate_table(&update.table)?;
7465
7466        let mysql_like_update_from = matches!(
7467            self.config.dialect,
7468            Some(DialectType::MySQL) | Some(DialectType::SingleStore)
7469        ) && update.from_clause.is_some();
7470
7471        let mut set_pairs = update.set.clone();
7472
7473        // MySQL-style UPDATE doesn't support FROM after SET. Convert FROM tables to JOIN ... ON TRUE.
7474        let mut pre_set_joins = update.table_joins.clone();
7475        if mysql_like_update_from {
7476            let target_name = update
7477                .table
7478                .alias
7479                .as_ref()
7480                .map(|a| a.name.clone())
7481                .unwrap_or_else(|| update.table.name.name.clone());
7482
7483            for (col, _) in &mut set_pairs {
7484                if !col.name.contains('.') {
7485                    col.name = format!("{}.{}", target_name, col.name);
7486                }
7487            }
7488
7489            if let Some(from_clause) = &update.from_clause {
7490                for table_expr in &from_clause.expressions {
7491                    pre_set_joins.push(crate::expressions::Join {
7492                        this: table_expr.clone(),
7493                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7494                            value: true,
7495                        })),
7496                        using: Vec::new(),
7497                        kind: crate::expressions::JoinKind::Inner,
7498                        use_inner_keyword: false,
7499                        use_outer_keyword: false,
7500                        deferred_condition: false,
7501                        join_hint: None,
7502                        match_condition: None,
7503                        pivots: Vec::new(),
7504                        comments: Vec::new(),
7505                        nesting_group: 0,
7506                        directed: false,
7507                    });
7508                }
7509            }
7510            for join in &update.from_joins {
7511                let mut join = join.clone();
7512                if join.on.is_none() && join.using.is_empty() {
7513                    join.on = Some(Expression::Boolean(crate::expressions::BooleanLiteral {
7514                        value: true,
7515                    }));
7516                }
7517                pre_set_joins.push(join);
7518            }
7519        }
7520
7521        // Extra tables for multi-table UPDATE (MySQL syntax)
7522        for extra_table in &update.extra_tables {
7523            self.write(", ");
7524            self.generate_table(extra_table)?;
7525        }
7526
7527        // JOINs attached to the table list (MySQL multi-table syntax)
7528        for join in &pre_set_joins {
7529            // generate_join already adds a leading space
7530            self.generate_join(join)?;
7531        }
7532
7533        // Teradata: FROM clause comes before SET
7534        let teradata_from_before_set = matches!(self.config.dialect, Some(DialectType::Teradata));
7535        if teradata_from_before_set && !mysql_like_update_from {
7536            if let Some(ref from_clause) = update.from_clause {
7537                self.write_space();
7538                self.write_keyword("FROM");
7539                self.write_space();
7540                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7541                    if i > 0 {
7542                        self.write(", ");
7543                    }
7544                    self.generate_expression(table_expr)?;
7545                }
7546            }
7547            for join in &update.from_joins {
7548                self.generate_join(join)?;
7549            }
7550        }
7551
7552        self.write_space();
7553        self.write_keyword("SET");
7554        self.write_space();
7555
7556        for (i, (col, val)) in set_pairs.iter().enumerate() {
7557            if i > 0 {
7558                self.write(", ");
7559            }
7560            self.generate_identifier(col)?;
7561            self.write(" = ");
7562            self.generate_expression(val)?;
7563        }
7564
7565        // OUTPUT clause (TSQL)
7566        if let Some(ref output) = update.output {
7567            self.generate_output_clause(output)?;
7568        }
7569
7570        // FROM clause (after SET for non-Teradata, non-MySQL dialects)
7571        if !mysql_like_update_from && !teradata_from_before_set {
7572            if let Some(ref from_clause) = update.from_clause {
7573                self.write_space();
7574                self.write_keyword("FROM");
7575                self.write_space();
7576                // Generate each table in the FROM clause
7577                for (i, table_expr) in from_clause.expressions.iter().enumerate() {
7578                    if i > 0 {
7579                        self.write(", ");
7580                    }
7581                    self.generate_expression(table_expr)?;
7582                }
7583            }
7584        }
7585
7586        if !mysql_like_update_from && !teradata_from_before_set {
7587            // JOINs after FROM clause (PostgreSQL, Snowflake, SQL Server syntax)
7588            for join in &update.from_joins {
7589                self.generate_join(join)?;
7590            }
7591        }
7592
7593        if let Some(where_clause) = &update.where_clause {
7594            self.write_space();
7595            self.write_keyword("WHERE");
7596            self.write_space();
7597            self.generate_expression(&where_clause.this)?;
7598        }
7599
7600        // RETURNING clause
7601        if !update.returning.is_empty() {
7602            self.write_space();
7603            self.write_keyword("RETURNING");
7604            self.write_space();
7605            for (i, expr) in update.returning.iter().enumerate() {
7606                if i > 0 {
7607                    self.write(", ");
7608                }
7609                self.generate_expression(expr)?;
7610            }
7611        }
7612
7613        // ORDER BY clause (MySQL)
7614        if let Some(ref order_by) = update.order_by {
7615            self.write_space();
7616            self.generate_order_by(order_by)?;
7617        }
7618
7619        // LIMIT clause (MySQL)
7620        if let Some(ref limit) = update.limit {
7621            self.write_space();
7622            self.write_keyword("LIMIT");
7623            self.write_space();
7624            self.generate_expression(limit)?;
7625        }
7626
7627        Ok(())
7628    }
7629
7630    fn generate_delete(&mut self, delete: &Delete) -> Result<()> {
7631        // Output WITH clause if present
7632        if let Some(with) = &delete.with {
7633            self.generate_with(with)?;
7634            self.write_space();
7635        }
7636
7637        // Output leading comments before DELETE
7638        for comment in &delete.leading_comments {
7639            self.write_formatted_comment(comment);
7640            self.write(" ");
7641        }
7642
7643        // MySQL multi-table DELETE or TSQL DELETE with OUTPUT before FROM
7644        if !delete.tables.is_empty() && !delete.tables_from_using {
7645            // DELETE t1[, t2] [OUTPUT ...] FROM ... syntax (tables before FROM)
7646            self.write_keyword("DELETE");
7647            if let Some(hint) = &delete.hint {
7648                self.generate_hint(hint)?;
7649            }
7650            self.write_space();
7651            for (i, tbl) in delete.tables.iter().enumerate() {
7652                if i > 0 {
7653                    self.write(", ");
7654                }
7655                self.generate_table(tbl)?;
7656            }
7657            // TSQL: OUTPUT clause between target table and FROM
7658            if let Some(ref output) = delete.output {
7659                self.generate_output_clause(output)?;
7660            }
7661            self.write_space();
7662            self.write_keyword("FROM");
7663            self.write_space();
7664            self.generate_table(&delete.table)?;
7665        } else if !delete.tables.is_empty() && delete.tables_from_using {
7666            // DELETE FROM t1, t2 USING ... syntax (tables after FROM)
7667            self.write_keyword("DELETE");
7668            if let Some(hint) = &delete.hint {
7669                self.generate_hint(hint)?;
7670            }
7671            self.write_space();
7672            self.write_keyword("FROM");
7673            self.write_space();
7674            for (i, tbl) in delete.tables.iter().enumerate() {
7675                if i > 0 {
7676                    self.write(", ");
7677                }
7678                self.generate_table(tbl)?;
7679            }
7680        } else if delete.no_from && matches!(self.config.dialect, Some(DialectType::BigQuery)) {
7681            // BigQuery-style DELETE without FROM keyword
7682            self.write_keyword("DELETE");
7683            if let Some(hint) = &delete.hint {
7684                self.generate_hint(hint)?;
7685            }
7686            self.write_space();
7687            self.generate_table(&delete.table)?;
7688        } else {
7689            self.write_keyword("DELETE");
7690            if let Some(hint) = &delete.hint {
7691                self.generate_hint(hint)?;
7692            }
7693            self.write_space();
7694            self.write_keyword("FROM");
7695            self.write_space();
7696            self.generate_table(&delete.table)?;
7697        }
7698
7699        // ClickHouse: ON CLUSTER clause
7700        if let Some(ref on_cluster) = delete.on_cluster {
7701            self.write_space();
7702            self.generate_on_cluster(on_cluster)?;
7703        }
7704
7705        // FORCE INDEX hint (MySQL)
7706        if let Some(ref idx) = delete.force_index {
7707            self.write_space();
7708            self.write_keyword("FORCE INDEX");
7709            self.write(" (");
7710            self.write(idx);
7711            self.write(")");
7712        }
7713
7714        // Optional alias
7715        if let Some(ref alias) = delete.alias {
7716            self.write_space();
7717            if delete.alias_explicit_as
7718                || matches!(self.config.dialect, Some(DialectType::BigQuery))
7719            {
7720                self.write_keyword("AS");
7721                self.write_space();
7722            }
7723            self.generate_identifier(alias)?;
7724        }
7725
7726        // JOINs (MySQL multi-table) - when NOT tables_from_using, JOINs come before USING
7727        if !delete.tables_from_using {
7728            for join in &delete.joins {
7729                self.generate_join(join)?;
7730            }
7731        }
7732
7733        // USING clause (PostgreSQL/DuckDB/MySQL)
7734        if !delete.using.is_empty() {
7735            self.write_space();
7736            self.write_keyword("USING");
7737            for (i, table) in delete.using.iter().enumerate() {
7738                if i > 0 {
7739                    self.write(",");
7740                }
7741                self.write_space();
7742                // Check if the table has subquery hints (DuckDB USING with subquery)
7743                if !table.hints.is_empty() && table.name.is_empty() {
7744                    // Subquery in USING: (VALUES ...) AS alias(cols)
7745                    self.generate_expression(&table.hints[0])?;
7746                    if let Some(ref alias) = table.alias {
7747                        self.write_space();
7748                        if table.alias_explicit_as {
7749                            self.write_keyword("AS");
7750                            self.write_space();
7751                        }
7752                        self.generate_identifier(alias)?;
7753                        if !table.column_aliases.is_empty() {
7754                            self.write("(");
7755                            for (j, col_alias) in table.column_aliases.iter().enumerate() {
7756                                if j > 0 {
7757                                    self.write(", ");
7758                                }
7759                                self.generate_identifier(col_alias)?;
7760                            }
7761                            self.write(")");
7762                        }
7763                    }
7764                } else {
7765                    self.generate_table(table)?;
7766                }
7767            }
7768        }
7769
7770        // JOINs (MySQL multi-table) - when tables_from_using, JOINs come after USING
7771        if delete.tables_from_using {
7772            for join in &delete.joins {
7773                self.generate_join(join)?;
7774            }
7775        }
7776
7777        // OUTPUT clause (TSQL) - only if not already emitted in the early position
7778        let output_already_emitted =
7779            !delete.tables.is_empty() && !delete.tables_from_using && delete.output.is_some();
7780        if !output_already_emitted {
7781            if let Some(ref output) = delete.output {
7782                self.generate_output_clause(output)?;
7783            }
7784        }
7785
7786        if let Some(where_clause) = &delete.where_clause {
7787            self.write_space();
7788            self.write_keyword("WHERE");
7789            self.write_space();
7790            self.generate_expression(&where_clause.this)?;
7791        }
7792
7793        // ORDER BY clause (MySQL)
7794        if let Some(ref order_by) = delete.order_by {
7795            self.write_space();
7796            self.generate_order_by(order_by)?;
7797        }
7798
7799        // LIMIT clause (MySQL)
7800        if let Some(ref limit) = delete.limit {
7801            self.write_space();
7802            self.write_keyword("LIMIT");
7803            self.write_space();
7804            self.generate_expression(limit)?;
7805        }
7806
7807        // RETURNING clause (PostgreSQL)
7808        if !delete.returning.is_empty() {
7809            self.write_space();
7810            self.write_keyword("RETURNING");
7811            self.write_space();
7812            for (i, expr) in delete.returning.iter().enumerate() {
7813                if i > 0 {
7814                    self.write(", ");
7815                }
7816                self.generate_expression(expr)?;
7817            }
7818        }
7819
7820        Ok(())
7821    }
7822
7823    // ==================== DDL Generation ====================
7824
7825    fn generate_create_table(&mut self, ct: &CreateTable) -> Result<()> {
7826        // Athena: Determine if this is Hive-style DDL or Trino-style DML
7827        // CREATE TABLE AS SELECT uses Trino (double quotes)
7828        // CREATE TABLE (without AS SELECT) and CREATE EXTERNAL TABLE use Hive (backticks)
7829        let saved_athena_hive_context = self.athena_hive_context;
7830        let is_clickhouse = matches!(self.config.dialect, Some(DialectType::ClickHouse));
7831        if matches!(
7832            self.config.dialect,
7833            Some(crate::dialects::DialectType::Athena)
7834        ) {
7835            // Use Hive context if:
7836            // 1. It's an EXTERNAL table, OR
7837            // 2. There's no AS SELECT clause
7838            let is_external = ct
7839                .table_modifier
7840                .as_ref()
7841                .map(|m| m.eq_ignore_ascii_case("EXTERNAL"))
7842                .unwrap_or(false);
7843            let has_as_select = ct.as_select.is_some();
7844            self.athena_hive_context = is_external || !has_as_select;
7845        }
7846
7847        // TSQL: Convert CREATE TABLE AS SELECT to SELECT * INTO table FROM (subquery) AS temp
7848        if matches!(
7849            self.config.dialect,
7850            Some(crate::dialects::DialectType::TSQL)
7851        ) {
7852            if let Some(ref query) = ct.as_select {
7853                // Output WITH CTE clause if present
7854                if let Some(with_cte) = &ct.with_cte {
7855                    self.generate_with(with_cte)?;
7856                    self.write_space();
7857                }
7858
7859                // Generate: SELECT * INTO [table] FROM (subquery) AS temp
7860                self.write_keyword("SELECT");
7861                self.write(" * ");
7862                self.write_keyword("INTO");
7863                self.write_space();
7864
7865                // If temporary, prefix with # for TSQL temp table
7866                if ct.temporary {
7867                    self.write("#");
7868                }
7869                self.generate_table(&ct.name)?;
7870
7871                self.write_space();
7872                self.write_keyword("FROM");
7873                self.write(" (");
7874                // For TSQL, add aliases to select columns to preserve column names
7875                let aliased_query = Self::add_column_aliases_to_query(query.clone());
7876                self.generate_expression(&aliased_query)?;
7877                self.write(") ");
7878                self.write_keyword("AS");
7879                self.write(" temp");
7880                return Ok(());
7881            }
7882        }
7883
7884        // Output WITH CTE clause if present
7885        if let Some(with_cte) = &ct.with_cte {
7886            self.generate_with(with_cte)?;
7887            self.write_space();
7888        }
7889
7890        // Output leading comments before CREATE
7891        for comment in &ct.leading_comments {
7892            self.write_formatted_comment(comment);
7893            self.write(" ");
7894        }
7895        self.write_keyword("CREATE");
7896
7897        if ct.or_replace {
7898            self.write_space();
7899            self.write_keyword("OR REPLACE");
7900        }
7901
7902        if ct.temporary {
7903            self.write_space();
7904            // Oracle uses GLOBAL TEMPORARY TABLE syntax
7905            if matches!(self.config.dialect, Some(DialectType::Oracle)) {
7906                self.write_keyword("GLOBAL TEMPORARY");
7907            } else {
7908                self.write_keyword("TEMPORARY");
7909            }
7910        }
7911
7912        // Table modifier: DYNAMIC, ICEBERG, EXTERNAL, HYBRID, TRANSIENT
7913        let is_dictionary = ct
7914            .table_modifier
7915            .as_ref()
7916            .map(|m| m.eq_ignore_ascii_case("DICTIONARY"))
7917            .unwrap_or(false);
7918        if let Some(ref modifier) = ct.table_modifier {
7919            // TRANSIENT is Snowflake-specific - skip for other dialects
7920            let skip_transient = modifier.eq_ignore_ascii_case("TRANSIENT")
7921                && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None);
7922            // Teradata-specific modifiers: VOLATILE, SET, MULTISET, SET TABLE combinations
7923            let is_teradata_modifier = modifier.eq_ignore_ascii_case("VOLATILE")
7924                || modifier.eq_ignore_ascii_case("SET")
7925                || modifier.eq_ignore_ascii_case("MULTISET")
7926                || modifier.to_ascii_uppercase().contains("VOLATILE")
7927                || modifier.to_ascii_uppercase().starts_with("SET ")
7928                || modifier.to_ascii_uppercase().starts_with("MULTISET ");
7929            let skip_teradata =
7930                is_teradata_modifier && !matches!(self.config.dialect, Some(DialectType::Teradata));
7931            if !skip_transient && !skip_teradata {
7932                self.write_space();
7933                self.write_keyword(modifier);
7934            }
7935        }
7936
7937        if !is_dictionary {
7938            self.write_space();
7939            self.write_keyword("TABLE");
7940        }
7941
7942        if ct.if_not_exists {
7943            self.write_space();
7944            self.write_keyword("IF NOT EXISTS");
7945        }
7946
7947        self.write_space();
7948        self.generate_table(&ct.name)?;
7949
7950        // ClickHouse: UUID 'xxx' clause after table name
7951        if let Some(ref uuid) = ct.uuid {
7952            self.write_space();
7953            self.write_keyword("UUID");
7954            self.write(" '");
7955            self.write(uuid);
7956            self.write("'");
7957        }
7958
7959        // ClickHouse: ON CLUSTER clause
7960        if let Some(ref on_cluster) = ct.on_cluster {
7961            self.write_space();
7962            self.generate_on_cluster(on_cluster)?;
7963        }
7964
7965        // Teradata: options after table name before column list (comma-separated)
7966        if matches!(
7967            self.config.dialect,
7968            Some(crate::dialects::DialectType::Teradata)
7969        ) && !ct.teradata_post_name_options.is_empty()
7970        {
7971            for opt in &ct.teradata_post_name_options {
7972                self.write(", ");
7973                self.write(opt);
7974            }
7975        }
7976
7977        // Snowflake: COPY GRANTS clause
7978        if ct.copy_grants {
7979            self.write_space();
7980            self.write_keyword("COPY GRANTS");
7981        }
7982
7983        // Snowflake: USING TEMPLATE clause (before columns or AS SELECT)
7984        if let Some(ref using_template) = ct.using_template {
7985            self.write_space();
7986            self.write_keyword("USING TEMPLATE");
7987            self.write_space();
7988            self.generate_expression(using_template)?;
7989            return Ok(());
7990        }
7991
7992        // ClickHouse uses CREATE TABLE target AS source [ENGINE ...] for table-structure copies.
7993        // When explicit columns or constraints are present, the source must be emitted
7994        // after the parenthesized schema: CREATE TABLE target (cols) AS source.
7995        if is_clickhouse {
7996            if let Some(ref clone_source) = ct.clone_source {
7997                if ct.columns.is_empty() && ct.constraints.is_empty() {
7998                    self.write_space();
7999                    self.write_keyword("AS");
8000                    self.write_space();
8001                    self.generate_table(clone_source)?;
8002                }
8003            }
8004        }
8005
8006        // Handle [SHALLOW | DEEP] CLONE/COPY source_table [AT(...) | BEFORE(...)]
8007        if !is_clickhouse {
8008            if let Some(ref clone_source) = ct.clone_source {
8009                self.write_space();
8010                if ct.is_copy && self.config.supports_table_copy {
8011                    // BigQuery uses COPY
8012                    self.write_keyword("COPY");
8013                } else if ct.shallow_clone {
8014                    self.write_keyword("SHALLOW CLONE");
8015                } else if ct.deep_clone {
8016                    self.write_keyword("DEEP CLONE");
8017                } else {
8018                    self.write_keyword("CLONE");
8019                }
8020                self.write_space();
8021                self.generate_table(clone_source)?;
8022                // Generate AT/BEFORE time travel clause (stored as Raw expression)
8023                if let Some(ref at_clause) = ct.clone_at_clause {
8024                    self.write_space();
8025                    self.generate_expression(at_clause)?;
8026                }
8027                return Ok(());
8028            }
8029        }
8030
8031        // Handle PARTITION OF property
8032        // Output order: PARTITION OF <table> (<columns/constraints>) FOR VALUES ...
8033        // Columns/constraints must appear BETWEEN the table name and the partition bound spec
8034        if let Some(ref partition_of) = ct.partition_of {
8035            self.write_space();
8036
8037            // Extract the PartitionedOfProperty parts to generate them separately
8038            if let Expression::PartitionedOfProperty(ref pop) = partition_of {
8039                // Output: PARTITION OF <table>
8040                self.write_keyword("PARTITION OF");
8041                self.write_space();
8042                self.generate_expression(&pop.this)?;
8043
8044                // Output columns/constraints if present (e.g., (unitsales DEFAULT 0) or (CONSTRAINT ...))
8045                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
8046                    self.write(" (");
8047                    let mut first = true;
8048                    for col in &ct.columns {
8049                        if !first {
8050                            self.write(", ");
8051                        }
8052                        first = false;
8053                        self.generate_column_def(col)?;
8054                    }
8055                    for constraint in &ct.constraints {
8056                        if !first {
8057                            self.write(", ");
8058                        }
8059                        first = false;
8060                        self.generate_table_constraint(constraint)?;
8061                    }
8062                    self.write(")");
8063                }
8064
8065                // Output partition bound spec: FOR VALUES ... or DEFAULT
8066                if let Expression::PartitionBoundSpec(_) = pop.expression.as_ref() {
8067                    self.write_space();
8068                    self.write_keyword("FOR VALUES");
8069                    self.write_space();
8070                    self.generate_expression(&pop.expression)?;
8071                } else {
8072                    self.write_space();
8073                    self.write_keyword("DEFAULT");
8074                }
8075            } else {
8076                // Fallback: generate the whole expression if it's not a PartitionedOfProperty
8077                self.generate_expression(partition_of)?;
8078
8079                // Output columns/constraints if present
8080                if !ct.columns.is_empty() || !ct.constraints.is_empty() {
8081                    self.write(" (");
8082                    let mut first = true;
8083                    for col in &ct.columns {
8084                        if !first {
8085                            self.write(", ");
8086                        }
8087                        first = false;
8088                        self.generate_column_def(col)?;
8089                    }
8090                    for constraint in &ct.constraints {
8091                        if !first {
8092                            self.write(", ");
8093                        }
8094                        first = false;
8095                        self.generate_table_constraint(constraint)?;
8096                    }
8097                    self.write(")");
8098                }
8099            }
8100
8101            // Output table properties (e.g., PARTITION BY RANGE(population))
8102            for prop in &ct.properties {
8103                self.write_space();
8104                self.generate_expression(prop)?;
8105            }
8106
8107            return Ok(());
8108        }
8109
8110        // SQLite: Inline single-column PRIMARY KEY constraints into column definition
8111        // This matches Python sqlglot's behavior for SQLite dialect
8112        self.sqlite_inline_pk_columns.clear();
8113        if matches!(
8114            self.config.dialect,
8115            Some(crate::dialects::DialectType::SQLite)
8116        ) {
8117            for constraint in &ct.constraints {
8118                if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8119                    // Only inline if: single column, no constraint name, and column exists in table
8120                    if columns.len() == 1 && name.is_none() {
8121                        let pk_col_name = columns[0].name.to_ascii_lowercase();
8122                        // Check if this column exists in the table
8123                        if ct
8124                            .columns
8125                            .iter()
8126                            .any(|c| c.name.name.to_ascii_lowercase() == pk_col_name)
8127                        {
8128                            self.sqlite_inline_pk_columns.insert(pk_col_name);
8129                        }
8130                    }
8131                }
8132            }
8133        }
8134
8135        // Output columns if present (even for CTAS with columns)
8136        if !ct.columns.is_empty() {
8137            if self.config.pretty {
8138                // Pretty print: each column on new line
8139                self.write(" (");
8140                self.write_newline();
8141                self.indent_level += 1;
8142                for (i, col) in ct.columns.iter().enumerate() {
8143                    if i > 0 {
8144                        self.write(",");
8145                        self.write_newline();
8146                    }
8147                    self.write_indent();
8148                    self.generate_column_def(col)?;
8149                }
8150                // Table constraints (skip inlined PRIMARY KEY for SQLite)
8151                for constraint in &ct.constraints {
8152                    // Skip single-column PRIMARY KEY that was inlined for SQLite
8153                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8154                        if columns.len() == 1
8155                            && name.is_none()
8156                            && self
8157                                .sqlite_inline_pk_columns
8158                                .contains(&columns[0].name.to_ascii_lowercase())
8159                        {
8160                            continue;
8161                        }
8162                    }
8163                    self.write(",");
8164                    self.write_newline();
8165                    self.write_indent();
8166                    self.generate_table_constraint(constraint)?;
8167                }
8168                self.indent_level -= 1;
8169                self.write_newline();
8170                self.write(")");
8171            } else {
8172                self.write(" (");
8173                for (i, col) in ct.columns.iter().enumerate() {
8174                    if i > 0 {
8175                        self.write(", ");
8176                    }
8177                    self.generate_column_def(col)?;
8178                }
8179                // Table constraints (skip inlined PRIMARY KEY for SQLite)
8180                let mut first_constraint = true;
8181                for constraint in &ct.constraints {
8182                    // Skip single-column PRIMARY KEY that was inlined for SQLite
8183                    if let TableConstraint::PrimaryKey { columns, name, .. } = constraint {
8184                        if columns.len() == 1
8185                            && name.is_none()
8186                            && self
8187                                .sqlite_inline_pk_columns
8188                                .contains(&columns[0].name.to_ascii_lowercase())
8189                        {
8190                            continue;
8191                        }
8192                    }
8193                    if first_constraint {
8194                        self.write(", ");
8195                        first_constraint = false;
8196                    } else {
8197                        self.write(", ");
8198                    }
8199                    self.generate_table_constraint(constraint)?;
8200                }
8201                self.write(")");
8202            }
8203        } else if !ct.constraints.is_empty() {
8204            // No columns but constraints exist (e.g., CREATE TABLE A LIKE B or CREATE TABLE A TAG (...))
8205            let has_like_only = ct
8206                .constraints
8207                .iter()
8208                .all(|c| matches!(c, TableConstraint::Like { .. }));
8209            let has_tags_only = ct
8210                .constraints
8211                .iter()
8212                .all(|c| matches!(c, TableConstraint::Tags(_)));
8213            // PostgreSQL: CREATE TABLE A (LIKE B INCLUDING ALL) (with parens)
8214            // Most dialects: CREATE TABLE A LIKE B (no parens)
8215            // Snowflake: CREATE TABLE A TAG (...) (no outer parens, but TAG has its own)
8216            let is_pg_like = matches!(
8217                self.config.dialect,
8218                Some(crate::dialects::DialectType::PostgreSQL)
8219                    | Some(crate::dialects::DialectType::CockroachDB)
8220                    | Some(crate::dialects::DialectType::Materialize)
8221                    | Some(crate::dialects::DialectType::RisingWave)
8222                    | Some(crate::dialects::DialectType::Redshift)
8223                    | Some(crate::dialects::DialectType::Presto)
8224                    | Some(crate::dialects::DialectType::Trino)
8225                    | Some(crate::dialects::DialectType::Athena)
8226            );
8227            let use_parens = if has_like_only {
8228                is_pg_like
8229            } else {
8230                !has_tags_only
8231            };
8232            if self.config.pretty && use_parens {
8233                self.write(" (");
8234                self.write_newline();
8235                self.indent_level += 1;
8236                for (i, constraint) in ct.constraints.iter().enumerate() {
8237                    if i > 0 {
8238                        self.write(",");
8239                        self.write_newline();
8240                    }
8241                    self.write_indent();
8242                    self.generate_table_constraint(constraint)?;
8243                }
8244                self.indent_level -= 1;
8245                self.write_newline();
8246                self.write(")");
8247            } else {
8248                if use_parens {
8249                    self.write(" (");
8250                } else {
8251                    self.write_space();
8252                }
8253                for (i, constraint) in ct.constraints.iter().enumerate() {
8254                    if i > 0 {
8255                        self.write(", ");
8256                    }
8257                    self.generate_table_constraint(constraint)?;
8258                }
8259                if use_parens {
8260                    self.write(")");
8261                }
8262            }
8263        }
8264
8265        if is_clickhouse && (!ct.columns.is_empty() || !ct.constraints.is_empty()) {
8266            if let Some(ref clone_source) = ct.clone_source {
8267                self.write_space();
8268                self.write_keyword("AS");
8269                self.write_space();
8270                self.generate_table(clone_source)?;
8271            }
8272        }
8273
8274        // TSQL ON filegroup or ON filegroup (partition_column) clause
8275        if let Some(ref on_prop) = ct.on_property {
8276            self.write(" ");
8277            self.write_keyword("ON");
8278            self.write(" ");
8279            self.generate_expression(&on_prop.this)?;
8280        }
8281
8282        // BigQuery: WITH PARTITION COLUMNS (col_name col_type, ...)
8283        if !ct.with_partition_columns.is_empty() {
8284            if self.config.pretty {
8285                self.write_newline();
8286            } else {
8287                self.write_space();
8288            }
8289            self.write_keyword("WITH PARTITION COLUMNS");
8290            self.write(" (");
8291            if self.config.pretty {
8292                self.write_newline();
8293                self.indent_level += 1;
8294                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8295                    if i > 0 {
8296                        self.write(",");
8297                        self.write_newline();
8298                    }
8299                    self.write_indent();
8300                    self.generate_column_def(col)?;
8301                }
8302                self.indent_level -= 1;
8303                self.write_newline();
8304            } else {
8305                for (i, col) in ct.with_partition_columns.iter().enumerate() {
8306                    if i > 0 {
8307                        self.write(", ");
8308                    }
8309                    self.generate_column_def(col)?;
8310                }
8311            }
8312            self.write(")");
8313        }
8314
8315        // BigQuery: WITH CONNECTION `project.region.connection`
8316        if let Some(ref conn) = ct.with_connection {
8317            if self.config.pretty {
8318                self.write_newline();
8319            } else {
8320                self.write_space();
8321            }
8322            self.write_keyword("WITH CONNECTION");
8323            self.write_space();
8324            self.generate_table(conn)?;
8325        }
8326
8327        // Output SchemaCommentProperty BEFORE WITH properties (Presto/Hive/Spark style)
8328        // For ClickHouse, SchemaCommentProperty goes after AS SELECT, handled later
8329        if !is_clickhouse {
8330            for prop in &ct.properties {
8331                if let Expression::SchemaCommentProperty(_) = prop {
8332                    if self.config.pretty {
8333                        self.write_newline();
8334                    } else {
8335                        self.write_space();
8336                    }
8337                    self.generate_expression(prop)?;
8338                }
8339            }
8340        }
8341
8342        // WITH properties (output after columns if columns exist, otherwise before AS)
8343        if !ct.with_properties.is_empty() {
8344            // Snowflake ICEBERG/DYNAMIC TABLE: output properties inline (space-separated, no WITH wrapper)
8345            let is_snowflake_special_table = matches!(
8346                self.config.dialect,
8347                Some(crate::dialects::DialectType::Snowflake)
8348            ) && (ct.table_modifier.as_deref() == Some("ICEBERG")
8349                || ct.table_modifier.as_deref() == Some("DYNAMIC"));
8350            if is_snowflake_special_table {
8351                for (key, value) in &ct.with_properties {
8352                    self.write_space();
8353                    self.write(key);
8354                    self.write("=");
8355                    self.write(value);
8356                }
8357            } else if self.config.pretty {
8358                self.write_newline();
8359                self.write_keyword("WITH");
8360                self.write(" (");
8361                self.write_newline();
8362                self.indent_level += 1;
8363                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8364                    if i > 0 {
8365                        self.write(",");
8366                        self.write_newline();
8367                    }
8368                    self.write_indent();
8369                    self.write(key);
8370                    self.write("=");
8371                    self.write(value);
8372                }
8373                self.indent_level -= 1;
8374                self.write_newline();
8375                self.write(")");
8376            } else {
8377                self.write_space();
8378                self.write_keyword("WITH");
8379                self.write(" (");
8380                for (i, (key, value)) in ct.with_properties.iter().enumerate() {
8381                    if i > 0 {
8382                        self.write(", ");
8383                    }
8384                    self.write(key);
8385                    self.write("=");
8386                    self.write(value);
8387                }
8388                self.write(")");
8389            }
8390        }
8391
8392        let (pre_as_properties, post_as_properties): (Vec<&Expression>, Vec<&Expression>) =
8393            if is_clickhouse && ct.as_select.is_some() {
8394                let mut pre = Vec::new();
8395                let mut post = Vec::new();
8396                for prop in &ct.properties {
8397                    if matches!(prop, Expression::SchemaCommentProperty(_)) {
8398                        post.push(prop);
8399                    } else {
8400                        pre.push(prop);
8401                    }
8402                }
8403                (pre, post)
8404            } else {
8405                (ct.properties.iter().collect(), Vec::new())
8406            };
8407
8408        // Table properties like DEFAULT COLLATE (BigQuery), OPTIONS (...), TBLPROPERTIES (...), or PROPERTIES (...)
8409        for prop in pre_as_properties {
8410            // SchemaCommentProperty was already output before WITH properties (except for ClickHouse)
8411            if !is_clickhouse && matches!(prop, Expression::SchemaCommentProperty(_)) {
8412                continue;
8413            }
8414            if self.config.pretty {
8415                self.write_newline();
8416            } else {
8417                self.write_space();
8418            }
8419            // BigQuery: Properties containing OPTIONS should be wrapped with OPTIONS (...)
8420            // Hive: Properties should be wrapped with TBLPROPERTIES (...)
8421            // Doris/StarRocks: Properties should be wrapped with PROPERTIES (...)
8422            if let Expression::Properties(props) = prop {
8423                let is_hive_dialect = matches!(
8424                    self.config.dialect,
8425                    Some(crate::dialects::DialectType::Hive)
8426                        | Some(crate::dialects::DialectType::Spark)
8427                        | Some(crate::dialects::DialectType::Databricks)
8428                        | Some(crate::dialects::DialectType::Athena)
8429                );
8430                let is_doris_starrocks = matches!(
8431                    self.config.dialect,
8432                    Some(crate::dialects::DialectType::Doris)
8433                        | Some(crate::dialects::DialectType::StarRocks)
8434                );
8435                if is_hive_dialect {
8436                    self.generate_tblproperties_clause(&props.expressions)?;
8437                } else if is_doris_starrocks {
8438                    self.generate_properties_clause(&props.expressions)?;
8439                } else {
8440                    self.generate_options_clause(&props.expressions)?;
8441                }
8442            } else {
8443                self.generate_expression(prop)?;
8444            }
8445        }
8446
8447        // Post-table properties like TSQL WITH(SYSTEM_VERSIONING=ON(...)) or Doris PROPERTIES
8448        for prop in &ct.post_table_properties {
8449            if let Expression::WithSystemVersioningProperty(ref svp) = prop {
8450                self.write(" WITH(");
8451                self.generate_system_versioning_content(svp)?;
8452                self.write(")");
8453            } else if let Expression::Properties(props) = prop {
8454                // Doris/StarRocks: PROPERTIES ('key'='value', ...) in post_table_properties
8455                let is_doris_starrocks = matches!(
8456                    self.config.dialect,
8457                    Some(crate::dialects::DialectType::Doris)
8458                        | Some(crate::dialects::DialectType::StarRocks)
8459                );
8460                self.write_space();
8461                if is_doris_starrocks {
8462                    self.generate_properties_clause(&props.expressions)?;
8463                } else {
8464                    self.generate_options_clause(&props.expressions)?;
8465                }
8466            } else {
8467                self.write_space();
8468                self.generate_expression(prop)?;
8469            }
8470        }
8471
8472        // StarRocks ROLLUP property: ROLLUP (r1(col1, col2), r2(col1))
8473        // Only output for StarRocks target
8474        if let Some(ref rollup) = ct.rollup {
8475            if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
8476                self.write_space();
8477                self.generate_rollup_property(rollup)?;
8478            }
8479        }
8480
8481        // MySQL table options (ENGINE=val, AUTO_INCREMENT=val, etc.)
8482        // Only output for MySQL-compatible dialects; strip for others during transpilation
8483        // COMMENT is also used by Hive/Spark so we selectively preserve it
8484        let is_mysql_compatible = matches!(
8485            self.config.dialect,
8486            Some(DialectType::MySQL)
8487                | Some(DialectType::SingleStore)
8488                | Some(DialectType::Doris)
8489                | Some(DialectType::StarRocks)
8490                | None
8491        );
8492        let is_hive_compatible = matches!(
8493            self.config.dialect,
8494            Some(DialectType::Hive)
8495                | Some(DialectType::Spark)
8496                | Some(DialectType::Databricks)
8497                | Some(DialectType::Athena)
8498        );
8499        let mysql_pretty_options =
8500            self.config.pretty && matches!(self.config.dialect, Some(DialectType::MySQL));
8501        for (key, value) in &ct.mysql_table_options {
8502            // Skip non-MySQL-specific options for non-MySQL targets
8503            let should_output = if is_mysql_compatible {
8504                true
8505            } else if is_hive_compatible && key == "COMMENT" {
8506                true // COMMENT is valid in Hive/Spark table definitions
8507            } else {
8508                false
8509            };
8510            if should_output {
8511                if mysql_pretty_options {
8512                    self.write_newline();
8513                    self.write_indent();
8514                } else {
8515                    self.write_space();
8516                }
8517                self.write_keyword(key);
8518                // StarRocks/Doris: COMMENT 'value' (no =), others: COMMENT='value'
8519                if key == "COMMENT" && !self.config.schema_comment_with_eq {
8520                    self.write_space();
8521                } else {
8522                    self.write("=");
8523                }
8524                self.write(value);
8525            }
8526        }
8527
8528        // Spark/Databricks: USING PARQUET for temporary tables that don't already have a storage format
8529        if ct.temporary
8530            && matches!(
8531                self.config.dialect,
8532                Some(DialectType::Spark) | Some(DialectType::Databricks)
8533            )
8534            && ct.as_select.is_none()
8535        {
8536            self.write_space();
8537            self.write_keyword("USING PARQUET");
8538        }
8539
8540        // PostgreSQL INHERITS clause
8541        if !ct.inherits.is_empty() {
8542            self.write_space();
8543            self.write_keyword("INHERITS");
8544            self.write(" (");
8545            for (i, parent) in ct.inherits.iter().enumerate() {
8546                if i > 0 {
8547                    self.write(", ");
8548                }
8549                self.generate_table(parent)?;
8550            }
8551            self.write(")");
8552        }
8553
8554        // CREATE TABLE AS SELECT
8555        if let Some(ref query) = ct.as_select {
8556            self.write_space();
8557            self.write_keyword("AS");
8558            self.write_space();
8559            let source_is_clickhouse =
8560                matches!(self.config.source_dialect, Some(DialectType::ClickHouse));
8561            let wrap_as_select =
8562                ct.as_select_parenthesized && !(is_clickhouse && source_is_clickhouse);
8563            if wrap_as_select {
8564                self.write("(");
8565            }
8566            self.generate_expression(query)?;
8567            if wrap_as_select {
8568                self.write(")");
8569            }
8570
8571            // Teradata: WITH DATA / WITH NO DATA
8572            if let Some(with_data) = ct.with_data {
8573                self.write_space();
8574                self.write_keyword("WITH");
8575                if !with_data {
8576                    self.write_space();
8577                    self.write_keyword("NO");
8578                }
8579                self.write_space();
8580                self.write_keyword("DATA");
8581            }
8582
8583            // Teradata: AND STATISTICS / AND NO STATISTICS
8584            if let Some(with_statistics) = ct.with_statistics {
8585                self.write_space();
8586                self.write_keyword("AND");
8587                if !with_statistics {
8588                    self.write_space();
8589                    self.write_keyword("NO");
8590                }
8591                self.write_space();
8592                self.write_keyword("STATISTICS");
8593            }
8594
8595            // Teradata: Index specifications
8596            for index in &ct.teradata_indexes {
8597                self.write_space();
8598                match index.kind {
8599                    TeradataIndexKind::NoPrimary => {
8600                        self.write_keyword("NO PRIMARY INDEX");
8601                    }
8602                    TeradataIndexKind::Primary => {
8603                        self.write_keyword("PRIMARY INDEX");
8604                    }
8605                    TeradataIndexKind::PrimaryAmp => {
8606                        self.write_keyword("PRIMARY AMP INDEX");
8607                    }
8608                    TeradataIndexKind::Unique => {
8609                        self.write_keyword("UNIQUE INDEX");
8610                    }
8611                    TeradataIndexKind::UniquePrimary => {
8612                        self.write_keyword("UNIQUE PRIMARY INDEX");
8613                    }
8614                    TeradataIndexKind::Secondary => {
8615                        self.write_keyword("INDEX");
8616                    }
8617                }
8618                // Output index name if present
8619                if let Some(ref name) = index.name {
8620                    self.write_space();
8621                    self.write(name);
8622                }
8623                // Output columns if present
8624                if !index.columns.is_empty() {
8625                    self.write(" (");
8626                    for (i, col) in index.columns.iter().enumerate() {
8627                        if i > 0 {
8628                            self.write(", ");
8629                        }
8630                        self.write(col);
8631                    }
8632                    self.write(")");
8633                }
8634            }
8635
8636            // Teradata: ON COMMIT behavior for volatile tables
8637            if let Some(ref on_commit) = ct.on_commit {
8638                self.write_space();
8639                self.write_keyword("ON COMMIT");
8640                self.write_space();
8641                match on_commit {
8642                    OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8643                    OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8644                }
8645            }
8646
8647            if !post_as_properties.is_empty() {
8648                for prop in post_as_properties {
8649                    self.write_space();
8650                    self.generate_expression(prop)?;
8651                }
8652            }
8653
8654            // Restore Athena Hive context before early return
8655            self.athena_hive_context = saved_athena_hive_context;
8656            return Ok(());
8657        }
8658
8659        // ON COMMIT behavior (for non-CTAS tables)
8660        if let Some(ref on_commit) = ct.on_commit {
8661            self.write_space();
8662            self.write_keyword("ON COMMIT");
8663            self.write_space();
8664            match on_commit {
8665                OnCommit::PreserveRows => self.write_keyword("PRESERVE ROWS"),
8666                OnCommit::DeleteRows => self.write_keyword("DELETE ROWS"),
8667            }
8668        }
8669
8670        // Restore Athena Hive context
8671        self.athena_hive_context = saved_athena_hive_context;
8672
8673        Ok(())
8674    }
8675
8676    /// Generate column definition as an expression (for ROWS FROM alias columns, XMLTABLE/JSON_TABLE)
8677    /// Outputs: "col_name" TYPE [PATH 'xpath'] (not the full CREATE TABLE column definition)
8678    fn generate_column_def_expr(&mut self, col: &ColumnDef) -> Result<()> {
8679        // Output column name
8680        self.generate_identifier(&col.name)?;
8681        // Output data type if known
8682        if !matches!(col.data_type, DataType::Unknown) {
8683            self.write_space();
8684            self.generate_data_type(&col.data_type)?;
8685        }
8686        // Output PATH constraint if present (for XMLTABLE/JSON_TABLE columns)
8687        for constraint in &col.constraints {
8688            if let ColumnConstraint::Path(path_expr) = constraint {
8689                self.write_space();
8690                self.write_keyword("PATH");
8691                self.write_space();
8692                self.generate_expression(path_expr)?;
8693            }
8694        }
8695        Ok(())
8696    }
8697
8698    fn generate_column_def(&mut self, col: &ColumnDef) -> Result<()> {
8699        // Check if this is a TSQL computed column (no data type)
8700        let has_computed_no_type = matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8701            && col
8702                .constraints
8703                .iter()
8704                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8705        // Some dialects (notably TSQL/Fabric) do not include an explicit type for computed columns.
8706        let omit_computed_type = !self.config.computed_column_with_type
8707            && col
8708                .constraints
8709                .iter()
8710                .any(|c| matches!(c, ColumnConstraint::ComputedColumn(_)));
8711
8712        // Check if this is a partition column spec (no data type, type is Unknown)
8713        // This is used in PostgreSQL PARTITION OF syntax where columns only have constraints
8714        let is_partition_column_spec = matches!(col.data_type, DataType::Unknown);
8715
8716        // Check if this is a DYNAMIC TABLE column (no data type, empty Custom name, no constraints)
8717        // Also check the no_type flag for SQLite columns without types
8718        let has_no_type = col.no_type
8719            || (matches!(&col.data_type, DataType::Custom { name } if name.is_empty())
8720                && col.constraints.is_empty());
8721
8722        self.generate_identifier(&col.name)?;
8723
8724        // Check for SERIAL/BIGSERIAL/SMALLSERIAL expansion for Materialize and PostgreSQL
8725        let serial_expansion = if matches!(
8726            self.config.dialect,
8727            Some(DialectType::Materialize) | Some(DialectType::PostgreSQL)
8728        ) {
8729            if let DataType::Custom { ref name } = col.data_type {
8730                if name.eq_ignore_ascii_case("SERIAL") {
8731                    Some("INT")
8732                } else if name.eq_ignore_ascii_case("BIGSERIAL") {
8733                    Some("BIGINT")
8734                } else if name.eq_ignore_ascii_case("SMALLSERIAL") {
8735                    Some("SMALLINT")
8736                } else {
8737                    None
8738                }
8739            } else {
8740                None
8741            }
8742        } else {
8743            None
8744        };
8745
8746        if !has_computed_no_type && !omit_computed_type && !is_partition_column_spec && !has_no_type
8747        {
8748            self.write_space();
8749            // ClickHouse CREATE TABLE column types: suppress automatic Nullable wrapping
8750            // since ClickHouse uses explicit Nullable() in its type system.
8751            let saved_nullable_depth = self.clickhouse_nullable_depth;
8752            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
8753                self.clickhouse_nullable_depth = -1;
8754            }
8755            if let Some(int_type) = serial_expansion {
8756                // SERIAL -> INT (+ constraints added below)
8757                self.write_keyword(int_type);
8758            } else if col.unsigned && matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8759                // For DuckDB: convert unsigned integer types to their unsigned equivalents
8760                let unsigned_type = match &col.data_type {
8761                    DataType::Int { .. } => Some("UINTEGER"),
8762                    DataType::BigInt { .. } => Some("UBIGINT"),
8763                    DataType::SmallInt { .. } => Some("USMALLINT"),
8764                    DataType::TinyInt { .. } => Some("UTINYINT"),
8765                    _ => None,
8766                };
8767                if let Some(utype) = unsigned_type {
8768                    self.write_keyword(utype);
8769                } else {
8770                    self.generate_data_type(&col.data_type)?;
8771                }
8772            } else {
8773                self.generate_data_type(&col.data_type)?;
8774            }
8775            self.clickhouse_nullable_depth = saved_nullable_depth;
8776        }
8777
8778        // MySQL type modifiers (must come right after data type)
8779        // Skip UNSIGNED for DuckDB (already mapped to unsigned type above)
8780        if col.unsigned && !matches!(self.config.dialect, Some(DialectType::DuckDB)) {
8781            self.write_space();
8782            self.write_keyword("UNSIGNED");
8783        }
8784        if col.zerofill {
8785            self.write_space();
8786            self.write_keyword("ZEROFILL");
8787        }
8788
8789        // Teradata column attributes (must come right after data type, in specific order)
8790        // ORDER: CHARACTER SET, UPPERCASE, CASESPECIFIC, FORMAT, TITLE, INLINE LENGTH, COMPRESS
8791
8792        if let Some(ref charset) = col.character_set {
8793            self.write_space();
8794            self.write_keyword("CHARACTER SET");
8795            self.write_space();
8796            self.write(charset);
8797        }
8798
8799        if col.uppercase {
8800            self.write_space();
8801            self.write_keyword("UPPERCASE");
8802        }
8803
8804        if let Some(casespecific) = col.casespecific {
8805            self.write_space();
8806            if casespecific {
8807                self.write_keyword("CASESPECIFIC");
8808            } else {
8809                self.write_keyword("NOT CASESPECIFIC");
8810            }
8811        }
8812
8813        if let Some(ref format) = col.format {
8814            self.write_space();
8815            self.write_keyword("FORMAT");
8816            self.write(" '");
8817            self.write(format);
8818            self.write("'");
8819        }
8820
8821        if let Some(ref title) = col.title {
8822            self.write_space();
8823            self.write_keyword("TITLE");
8824            self.write(" '");
8825            self.write(title);
8826            self.write("'");
8827        }
8828
8829        if let Some(length) = col.inline_length {
8830            self.write_space();
8831            self.write_keyword("INLINE LENGTH");
8832            self.write(" ");
8833            self.write(&length.to_string());
8834        }
8835
8836        if let Some(ref compress) = col.compress {
8837            self.write_space();
8838            self.write_keyword("COMPRESS");
8839            if !compress.is_empty() {
8840                // Single string literal: output without parentheses (Teradata syntax)
8841                if compress.len() == 1 {
8842                    if let Expression::Literal(lit) = &compress[0] {
8843                        if let Literal::String(_) = lit.as_ref() {
8844                            self.write_space();
8845                            self.generate_expression(&compress[0])?;
8846                        }
8847                    } else {
8848                        self.write(" (");
8849                        self.generate_expression(&compress[0])?;
8850                        self.write(")");
8851                    }
8852                } else {
8853                    self.write(" (");
8854                    for (i, val) in compress.iter().enumerate() {
8855                        if i > 0 {
8856                            self.write(", ");
8857                        }
8858                        self.generate_expression(val)?;
8859                    }
8860                    self.write(")");
8861                }
8862            }
8863        }
8864
8865        // Column constraints - output in original order if constraint_order is populated
8866        // Otherwise fall back to legacy fixed order for backward compatibility
8867        if !col.constraint_order.is_empty() {
8868            // Use constraint_order for original ordering
8869            // Track indices for constraints stored in the constraints Vec
8870            let mut references_idx = 0;
8871            let mut check_idx = 0;
8872            let mut generated_idx = 0;
8873            let mut collate_idx = 0;
8874            let mut comment_idx = 0;
8875            // The preprocessing in dialects/mod.rs now handles the correct ordering of
8876            // NOT NULL relative to IDENTITY for PostgreSQL, so no deferral needed here.
8877            let defer_not_null_after_identity = false;
8878            let mut pending_not_null_after_identity = false;
8879
8880            for constraint_type in &col.constraint_order {
8881                match constraint_type {
8882                    ConstraintType::PrimaryKey => {
8883                        // Materialize doesn't support PRIMARY KEY column constraints
8884                        if col.primary_key
8885                            && !matches!(self.config.dialect, Some(DialectType::Materialize))
8886                        {
8887                            if let Some(ref cname) = col.primary_key_constraint_name {
8888                                self.write_space();
8889                                self.write_keyword("CONSTRAINT");
8890                                self.write_space();
8891                                self.write(cname);
8892                            }
8893                            self.write_space();
8894                            self.write_keyword("PRIMARY KEY");
8895                            if let Some(ref order) = col.primary_key_order {
8896                                self.write_space();
8897                                match order {
8898                                    SortOrder::Asc => self.write_keyword("ASC"),
8899                                    SortOrder::Desc => self.write_keyword("DESC"),
8900                                }
8901                            }
8902                        }
8903                    }
8904                    ConstraintType::Unique => {
8905                        if col.unique {
8906                            if let Some(ref cname) = col.unique_constraint_name {
8907                                self.write_space();
8908                                self.write_keyword("CONSTRAINT");
8909                                self.write_space();
8910                                self.write(cname);
8911                            }
8912                            self.write_space();
8913                            self.write_keyword("UNIQUE");
8914                            // PostgreSQL 15+: NULLS NOT DISTINCT
8915                            if col.unique_nulls_not_distinct {
8916                                self.write(" NULLS NOT DISTINCT");
8917                            }
8918                        }
8919                    }
8920                    ConstraintType::NotNull => {
8921                        if col.nullable == Some(false) {
8922                            if defer_not_null_after_identity {
8923                                pending_not_null_after_identity = true;
8924                                continue;
8925                            }
8926                            if let Some(ref cname) = col.not_null_constraint_name {
8927                                self.write_space();
8928                                self.write_keyword("CONSTRAINT");
8929                                self.write_space();
8930                                self.write(cname);
8931                            }
8932                            self.write_space();
8933                            self.write_keyword("NOT NULL");
8934                        }
8935                    }
8936                    ConstraintType::Null => {
8937                        if col.nullable == Some(true) {
8938                            self.write_space();
8939                            self.write_keyword("NULL");
8940                        }
8941                    }
8942                    ConstraintType::Default => {
8943                        if let Some(ref default) = col.default {
8944                            self.write_space();
8945                            self.write_keyword("DEFAULT");
8946                            self.write_space();
8947                            self.generate_expression(default)?;
8948                        }
8949                    }
8950                    ConstraintType::AutoIncrement => {
8951                        if col.auto_increment {
8952                            // DuckDB doesn't support AUTO_INCREMENT - skip entirely
8953                            if matches!(
8954                                self.config.dialect,
8955                                Some(crate::dialects::DialectType::DuckDB)
8956                            ) {
8957                                // Skip - DuckDB uses sequences or rowid instead
8958                            } else if matches!(
8959                                self.config.dialect,
8960                                Some(crate::dialects::DialectType::Materialize)
8961                            ) {
8962                                // Materialize strips AUTO_INCREMENT but adds NOT NULL
8963                                if !matches!(col.nullable, Some(false)) {
8964                                    self.write_space();
8965                                    self.write_keyword("NOT NULL");
8966                                }
8967                            } else if matches!(
8968                                self.config.dialect,
8969                                Some(crate::dialects::DialectType::PostgreSQL)
8970                            ) {
8971                                // PostgreSQL: AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
8972                                self.write_space();
8973                                self.generate_auto_increment_keyword(col)?;
8974                            } else if matches!(
8975                                self.config.dialect,
8976                                Some(crate::dialects::DialectType::SQLite)
8977                            ) && !col.primary_key
8978                                && self
8979                                    .sqlite_inline_pk_columns
8980                                    .contains(&col.name.name.to_ascii_lowercase())
8981                            {
8982                                // SQLite requires AUTOINCREMENT after PRIMARY KEY.
8983                                // The table-level primary key is emitted later inline.
8984                            } else {
8985                                self.write_space();
8986                                self.generate_auto_increment_keyword(col)?;
8987                                if pending_not_null_after_identity {
8988                                    self.write_space();
8989                                    self.write_keyword("NOT NULL");
8990                                    pending_not_null_after_identity = false;
8991                                }
8992                            }
8993                        } // close else for DuckDB skip
8994                    }
8995                    ConstraintType::References => {
8996                        // Find next References constraint
8997                        while references_idx < col.constraints.len() {
8998                            if let ColumnConstraint::References(fk_ref) =
8999                                &col.constraints[references_idx]
9000                            {
9001                                // CONSTRAINT name if present
9002                                if let Some(ref name) = fk_ref.constraint_name {
9003                                    self.write_space();
9004                                    self.write_keyword("CONSTRAINT");
9005                                    self.write_space();
9006                                    self.write(name);
9007                                }
9008                                self.write_space();
9009                                if fk_ref.has_foreign_key_keywords {
9010                                    self.write_keyword("FOREIGN KEY");
9011                                    self.write_space();
9012                                }
9013                                self.write_keyword("REFERENCES");
9014                                self.write_space();
9015                                self.generate_table(&fk_ref.table)?;
9016                                if !fk_ref.columns.is_empty() {
9017                                    self.write(" (");
9018                                    for (i, c) in fk_ref.columns.iter().enumerate() {
9019                                        if i > 0 {
9020                                            self.write(", ");
9021                                        }
9022                                        self.generate_identifier(c)?;
9023                                    }
9024                                    self.write(")");
9025                                }
9026                                self.generate_referential_actions(fk_ref)?;
9027                                references_idx += 1;
9028                                break;
9029                            }
9030                            references_idx += 1;
9031                        }
9032                    }
9033                    ConstraintType::Check => {
9034                        // Find next Check constraint
9035                        while check_idx < col.constraints.len() {
9036                            if let ColumnConstraint::Check(expr) = &col.constraints[check_idx] {
9037                                // Output CONSTRAINT name if present (only for first CHECK)
9038                                if check_idx == 0 {
9039                                    if let Some(ref cname) = col.check_constraint_name {
9040                                        self.write_space();
9041                                        self.write_keyword("CONSTRAINT");
9042                                        self.write_space();
9043                                        self.write(cname);
9044                                    }
9045                                }
9046                                self.write_space();
9047                                self.write_keyword("CHECK");
9048                                self.write(" (");
9049                                self.generate_expression(expr)?;
9050                                self.write(")");
9051                                check_idx += 1;
9052                                break;
9053                            }
9054                            check_idx += 1;
9055                        }
9056                    }
9057                    ConstraintType::GeneratedAsIdentity => {
9058                        // Find next GeneratedAsIdentity constraint
9059                        while generated_idx < col.constraints.len() {
9060                            if let ColumnConstraint::GeneratedAsIdentity(gen) =
9061                                &col.constraints[generated_idx]
9062                            {
9063                                self.write_space();
9064                                // Redshift uses IDENTITY(start, increment) syntax
9065                                if matches!(
9066                                    self.config.dialect,
9067                                    Some(crate::dialects::DialectType::Redshift)
9068                                ) {
9069                                    self.write_keyword("IDENTITY");
9070                                    self.write("(");
9071                                    if let Some(ref start) = gen.start {
9072                                        self.generate_expression(start)?;
9073                                    } else {
9074                                        self.write("0");
9075                                    }
9076                                    self.write(", ");
9077                                    if let Some(ref incr) = gen.increment {
9078                                        self.generate_expression(incr)?;
9079                                    } else {
9080                                        self.write("1");
9081                                    }
9082                                    self.write(")");
9083                                } else {
9084                                    self.write_keyword("GENERATED");
9085                                    if gen.always {
9086                                        self.write_space();
9087                                        self.write_keyword("ALWAYS");
9088                                    } else {
9089                                        self.write_space();
9090                                        self.write_keyword("BY DEFAULT");
9091                                        if gen.on_null {
9092                                            self.write_space();
9093                                            self.write_keyword("ON NULL");
9094                                        }
9095                                    }
9096                                    self.write_space();
9097                                    self.write_keyword("AS IDENTITY");
9098
9099                                    let has_options = gen.start.is_some()
9100                                        || gen.increment.is_some()
9101                                        || gen.minvalue.is_some()
9102                                        || gen.maxvalue.is_some()
9103                                        || gen.cycle.is_some();
9104                                    if has_options {
9105                                        self.write(" (");
9106                                        let mut first = true;
9107                                        if let Some(ref start) = gen.start {
9108                                            if !first {
9109                                                self.write(" ");
9110                                            }
9111                                            first = false;
9112                                            self.write_keyword("START WITH");
9113                                            self.write_space();
9114                                            self.generate_expression(start)?;
9115                                        }
9116                                        if let Some(ref incr) = gen.increment {
9117                                            if !first {
9118                                                self.write(" ");
9119                                            }
9120                                            first = false;
9121                                            self.write_keyword("INCREMENT BY");
9122                                            self.write_space();
9123                                            self.generate_expression(incr)?;
9124                                        }
9125                                        if let Some(ref minv) = gen.minvalue {
9126                                            if !first {
9127                                                self.write(" ");
9128                                            }
9129                                            first = false;
9130                                            self.write_keyword("MINVALUE");
9131                                            self.write_space();
9132                                            self.generate_expression(minv)?;
9133                                        }
9134                                        if let Some(ref maxv) = gen.maxvalue {
9135                                            if !first {
9136                                                self.write(" ");
9137                                            }
9138                                            first = false;
9139                                            self.write_keyword("MAXVALUE");
9140                                            self.write_space();
9141                                            self.generate_expression(maxv)?;
9142                                        }
9143                                        if let Some(cycle) = gen.cycle {
9144                                            if !first {
9145                                                self.write(" ");
9146                                            }
9147                                            if cycle {
9148                                                self.write_keyword("CYCLE");
9149                                            } else {
9150                                                self.write_keyword("NO CYCLE");
9151                                            }
9152                                        }
9153                                        self.write(")");
9154                                    }
9155                                }
9156                                generated_idx += 1;
9157                                break;
9158                            }
9159                            generated_idx += 1;
9160                        }
9161                    }
9162                    ConstraintType::Collate => {
9163                        // Find next Collate constraint
9164                        while collate_idx < col.constraints.len() {
9165                            if let ColumnConstraint::Collate(collation) =
9166                                &col.constraints[collate_idx]
9167                            {
9168                                self.write_space();
9169                                self.write_keyword("COLLATE");
9170                                self.write_space();
9171                                self.generate_identifier(collation)?;
9172                                collate_idx += 1;
9173                                break;
9174                            }
9175                            collate_idx += 1;
9176                        }
9177                    }
9178                    ConstraintType::Comment => {
9179                        // Find next Comment constraint
9180                        while comment_idx < col.constraints.len() {
9181                            if let ColumnConstraint::Comment(comment) =
9182                                &col.constraints[comment_idx]
9183                            {
9184                                self.write_space();
9185                                self.write_keyword("COMMENT");
9186                                self.write_space();
9187                                self.generate_string_literal(comment)?;
9188                                comment_idx += 1;
9189                                break;
9190                            }
9191                            comment_idx += 1;
9192                        }
9193                    }
9194                    ConstraintType::Tags => {
9195                        // Find next Tags constraint (Snowflake)
9196                        for constraint in &col.constraints {
9197                            if let ColumnConstraint::Tags(tags) = constraint {
9198                                self.write_space();
9199                                self.write_keyword("TAG");
9200                                self.write(" (");
9201                                for (i, expr) in tags.expressions.iter().enumerate() {
9202                                    if i > 0 {
9203                                        self.write(", ");
9204                                    }
9205                                    self.generate_expression(expr)?;
9206                                }
9207                                self.write(")");
9208                                break;
9209                            }
9210                        }
9211                    }
9212                    ConstraintType::ComputedColumn => {
9213                        // Find next ComputedColumn constraint
9214                        for constraint in &col.constraints {
9215                            if let ColumnConstraint::ComputedColumn(cc) = constraint {
9216                                self.write_space();
9217                                self.generate_computed_column_inline(cc)?;
9218                                break;
9219                            }
9220                        }
9221                    }
9222                    ConstraintType::GeneratedAsRow => {
9223                        // Find next GeneratedAsRow constraint
9224                        for constraint in &col.constraints {
9225                            if let ColumnConstraint::GeneratedAsRow(gar) = constraint {
9226                                self.write_space();
9227                                self.generate_generated_as_row_inline(gar)?;
9228                                break;
9229                            }
9230                        }
9231                    }
9232                    ConstraintType::OnUpdate => {
9233                        if let Some(ref expr) = col.on_update {
9234                            self.write_space();
9235                            self.write_keyword("ON UPDATE");
9236                            self.write_space();
9237                            self.generate_expression(expr)?;
9238                        }
9239                    }
9240                    ConstraintType::Encode => {
9241                        if let Some(ref encoding) = col.encoding {
9242                            self.write_space();
9243                            self.write_keyword("ENCODE");
9244                            self.write_space();
9245                            self.write(encoding);
9246                        }
9247                    }
9248                    ConstraintType::Path => {
9249                        // Find next Path constraint
9250                        for constraint in &col.constraints {
9251                            if let ColumnConstraint::Path(path_expr) = constraint {
9252                                self.write_space();
9253                                self.write_keyword("PATH");
9254                                self.write_space();
9255                                self.generate_expression(path_expr)?;
9256                                break;
9257                            }
9258                        }
9259                    }
9260                }
9261            }
9262            if pending_not_null_after_identity {
9263                self.write_space();
9264                self.write_keyword("NOT NULL");
9265            }
9266        } else {
9267            // Legacy fixed order for backward compatibility
9268            if col.primary_key {
9269                self.write_space();
9270                self.write_keyword("PRIMARY KEY");
9271                if let Some(ref order) = col.primary_key_order {
9272                    self.write_space();
9273                    match order {
9274                        SortOrder::Asc => self.write_keyword("ASC"),
9275                        SortOrder::Desc => self.write_keyword("DESC"),
9276                    }
9277                }
9278            }
9279
9280            if col.unique {
9281                self.write_space();
9282                self.write_keyword("UNIQUE");
9283                // PostgreSQL 15+: NULLS NOT DISTINCT
9284                if col.unique_nulls_not_distinct {
9285                    self.write(" NULLS NOT DISTINCT");
9286                }
9287            }
9288
9289            match col.nullable {
9290                Some(false) => {
9291                    self.write_space();
9292                    self.write_keyword("NOT NULL");
9293                }
9294                Some(true) => {
9295                    self.write_space();
9296                    self.write_keyword("NULL");
9297                }
9298                None => {}
9299            }
9300
9301            if let Some(ref default) = col.default {
9302                self.write_space();
9303                self.write_keyword("DEFAULT");
9304                self.write_space();
9305                self.generate_expression(default)?;
9306            }
9307
9308            if col.auto_increment {
9309                self.write_space();
9310                self.generate_auto_increment_keyword(col)?;
9311            }
9312
9313            // Column-level constraints from Vec
9314            for constraint in &col.constraints {
9315                match constraint {
9316                    ColumnConstraint::References(fk_ref) => {
9317                        self.write_space();
9318                        if fk_ref.has_foreign_key_keywords {
9319                            self.write_keyword("FOREIGN KEY");
9320                            self.write_space();
9321                        }
9322                        self.write_keyword("REFERENCES");
9323                        self.write_space();
9324                        self.generate_table(&fk_ref.table)?;
9325                        if !fk_ref.columns.is_empty() {
9326                            self.write(" (");
9327                            for (i, c) in fk_ref.columns.iter().enumerate() {
9328                                if i > 0 {
9329                                    self.write(", ");
9330                                }
9331                                self.generate_identifier(c)?;
9332                            }
9333                            self.write(")");
9334                        }
9335                        self.generate_referential_actions(fk_ref)?;
9336                    }
9337                    ColumnConstraint::Check(expr) => {
9338                        self.write_space();
9339                        self.write_keyword("CHECK");
9340                        self.write(" (");
9341                        self.generate_expression(expr)?;
9342                        self.write(")");
9343                    }
9344                    ColumnConstraint::GeneratedAsIdentity(gen) => {
9345                        self.write_space();
9346                        // Redshift uses IDENTITY(start, increment) syntax
9347                        if matches!(
9348                            self.config.dialect,
9349                            Some(crate::dialects::DialectType::Redshift)
9350                        ) {
9351                            self.write_keyword("IDENTITY");
9352                            self.write("(");
9353                            if let Some(ref start) = gen.start {
9354                                self.generate_expression(start)?;
9355                            } else {
9356                                self.write("0");
9357                            }
9358                            self.write(", ");
9359                            if let Some(ref incr) = gen.increment {
9360                                self.generate_expression(incr)?;
9361                            } else {
9362                                self.write("1");
9363                            }
9364                            self.write(")");
9365                        } else {
9366                            self.write_keyword("GENERATED");
9367                            if gen.always {
9368                                self.write_space();
9369                                self.write_keyword("ALWAYS");
9370                            } else {
9371                                self.write_space();
9372                                self.write_keyword("BY DEFAULT");
9373                                if gen.on_null {
9374                                    self.write_space();
9375                                    self.write_keyword("ON NULL");
9376                                }
9377                            }
9378                            self.write_space();
9379                            self.write_keyword("AS IDENTITY");
9380
9381                            let has_options = gen.start.is_some()
9382                                || gen.increment.is_some()
9383                                || gen.minvalue.is_some()
9384                                || gen.maxvalue.is_some()
9385                                || gen.cycle.is_some();
9386                            if has_options {
9387                                self.write(" (");
9388                                let mut first = true;
9389                                if let Some(ref start) = gen.start {
9390                                    if !first {
9391                                        self.write(" ");
9392                                    }
9393                                    first = false;
9394                                    self.write_keyword("START WITH");
9395                                    self.write_space();
9396                                    self.generate_expression(start)?;
9397                                }
9398                                if let Some(ref incr) = gen.increment {
9399                                    if !first {
9400                                        self.write(" ");
9401                                    }
9402                                    first = false;
9403                                    self.write_keyword("INCREMENT BY");
9404                                    self.write_space();
9405                                    self.generate_expression(incr)?;
9406                                }
9407                                if let Some(ref minv) = gen.minvalue {
9408                                    if !first {
9409                                        self.write(" ");
9410                                    }
9411                                    first = false;
9412                                    self.write_keyword("MINVALUE");
9413                                    self.write_space();
9414                                    self.generate_expression(minv)?;
9415                                }
9416                                if let Some(ref maxv) = gen.maxvalue {
9417                                    if !first {
9418                                        self.write(" ");
9419                                    }
9420                                    first = false;
9421                                    self.write_keyword("MAXVALUE");
9422                                    self.write_space();
9423                                    self.generate_expression(maxv)?;
9424                                }
9425                                if let Some(cycle) = gen.cycle {
9426                                    if !first {
9427                                        self.write(" ");
9428                                    }
9429                                    if cycle {
9430                                        self.write_keyword("CYCLE");
9431                                    } else {
9432                                        self.write_keyword("NO CYCLE");
9433                                    }
9434                                }
9435                                self.write(")");
9436                            }
9437                        }
9438                    }
9439                    ColumnConstraint::Collate(collation) => {
9440                        self.write_space();
9441                        self.write_keyword("COLLATE");
9442                        self.write_space();
9443                        self.generate_identifier(collation)?;
9444                    }
9445                    ColumnConstraint::Comment(comment) => {
9446                        self.write_space();
9447                        self.write_keyword("COMMENT");
9448                        self.write_space();
9449                        self.generate_string_literal(comment)?;
9450                    }
9451                    ColumnConstraint::Path(path_expr) => {
9452                        self.write_space();
9453                        self.write_keyword("PATH");
9454                        self.write_space();
9455                        self.generate_expression(path_expr)?;
9456                    }
9457                    _ => {} // Other constraints handled above
9458                }
9459            }
9460
9461            // Redshift: ENCODE encoding_type (legacy path)
9462            if let Some(ref encoding) = col.encoding {
9463                self.write_space();
9464                self.write_keyword("ENCODE");
9465                self.write_space();
9466                self.write(encoding);
9467            }
9468        }
9469
9470        // ClickHouse: CODEC(...)
9471        if let Some(ref codec) = col.codec {
9472            self.write_space();
9473            self.write_keyword("CODEC");
9474            self.write("(");
9475            self.write(codec);
9476            self.write(")");
9477        }
9478
9479        if let Some(visible) = col.visible {
9480            self.write_space();
9481            if visible {
9482                self.write_keyword("VISIBLE");
9483            } else {
9484                self.write_keyword("INVISIBLE");
9485            }
9486        }
9487
9488        // ClickHouse: EPHEMERAL [expr]
9489        if let Some(ref ephemeral) = col.ephemeral {
9490            self.write_space();
9491            self.write_keyword("EPHEMERAL");
9492            if let Some(ref expr) = ephemeral {
9493                self.write_space();
9494                self.generate_expression(expr)?;
9495            }
9496        }
9497
9498        // ClickHouse: MATERIALIZED expr
9499        if let Some(ref mat_expr) = col.materialized_expr {
9500            self.write_space();
9501            self.write_keyword("MATERIALIZED");
9502            self.write_space();
9503            self.generate_expression(mat_expr)?;
9504        }
9505
9506        // ClickHouse: ALIAS expr
9507        if let Some(ref alias_expr) = col.alias_expr {
9508            self.write_space();
9509            self.write_keyword("ALIAS");
9510            self.write_space();
9511            self.generate_expression(alias_expr)?;
9512        }
9513
9514        // ClickHouse: TTL expr
9515        if let Some(ref ttl_expr) = col.ttl_expr {
9516            self.write_space();
9517            self.write_keyword("TTL");
9518            self.write_space();
9519            self.generate_expression(ttl_expr)?;
9520        }
9521
9522        // TSQL: NOT FOR REPLICATION
9523        if col.not_for_replication
9524            && matches!(
9525                self.config.dialect,
9526                Some(crate::dialects::DialectType::TSQL)
9527                    | Some(crate::dialects::DialectType::Fabric)
9528            )
9529        {
9530            self.write_space();
9531            self.write_keyword("NOT FOR REPLICATION");
9532        }
9533
9534        // BigQuery: OPTIONS (key=value, ...) on column - comes after all constraints
9535        if !col.options.is_empty() {
9536            self.write_space();
9537            self.generate_options_clause(&col.options)?;
9538        }
9539
9540        // SQLite: Inline PRIMARY KEY from table constraint
9541        // This comes at the end, after all existing column constraints
9542        if !col.primary_key
9543            && self
9544                .sqlite_inline_pk_columns
9545                .contains(&col.name.name.to_ascii_lowercase())
9546        {
9547            self.write_space();
9548            self.write_keyword("PRIMARY KEY");
9549            if matches!(self.config.dialect, Some(DialectType::SQLite)) && col.auto_increment {
9550                self.write_space();
9551                self.generate_auto_increment_keyword(col)?;
9552            }
9553        }
9554
9555        // SERIAL expansion: add GENERATED BY DEFAULT AS IDENTITY NOT NULL for PostgreSQL,
9556        // just NOT NULL for Materialize (which strips GENERATED AS IDENTITY)
9557        if serial_expansion.is_some() {
9558            if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
9559                self.write_space();
9560                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY NOT NULL");
9561            } else if matches!(self.config.dialect, Some(DialectType::Materialize)) {
9562                self.write_space();
9563                self.write_keyword("NOT NULL");
9564            }
9565        }
9566
9567        Ok(())
9568    }
9569
9570    fn generate_table_constraint(&mut self, constraint: &TableConstraint) -> Result<()> {
9571        match constraint {
9572            TableConstraint::PrimaryKey {
9573                name,
9574                columns,
9575                include_columns,
9576                modifiers,
9577                has_constraint_keyword,
9578            } => {
9579                if let Some(ref n) = name {
9580                    if *has_constraint_keyword {
9581                        self.write_keyword("CONSTRAINT");
9582                        self.write_space();
9583                        self.generate_identifier(n)?;
9584                        self.write_space();
9585                    }
9586                }
9587                self.write_keyword("PRIMARY KEY");
9588                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9589                if let Some(ref clustered) = modifiers.clustered {
9590                    self.write_space();
9591                    self.write_keyword(clustered);
9592                }
9593                // MySQL format: PRIMARY KEY name (cols) when no CONSTRAINT keyword
9594                if let Some(ref n) = name {
9595                    if !*has_constraint_keyword {
9596                        self.write_space();
9597                        self.generate_identifier(n)?;
9598                    }
9599                }
9600                self.write(" (");
9601                for (i, col) in columns.iter().enumerate() {
9602                    if i > 0 {
9603                        self.write(", ");
9604                    }
9605                    self.generate_identifier(col)?;
9606                }
9607                self.write(")");
9608                if !include_columns.is_empty() {
9609                    self.write_space();
9610                    self.write_keyword("INCLUDE");
9611                    self.write(" (");
9612                    for (i, col) in include_columns.iter().enumerate() {
9613                        if i > 0 {
9614                            self.write(", ");
9615                        }
9616                        self.generate_identifier(col)?;
9617                    }
9618                    self.write(")");
9619                }
9620                self.generate_constraint_modifiers(modifiers);
9621            }
9622            TableConstraint::Unique {
9623                name,
9624                columns,
9625                columns_parenthesized,
9626                modifiers,
9627                has_constraint_keyword,
9628                nulls_not_distinct,
9629            } => {
9630                if let Some(ref n) = name {
9631                    if *has_constraint_keyword {
9632                        self.write_keyword("CONSTRAINT");
9633                        self.write_space();
9634                        self.generate_identifier(n)?;
9635                        self.write_space();
9636                    }
9637                }
9638                self.write_keyword("UNIQUE");
9639                // TSQL CLUSTERED/NONCLUSTERED modifier (before columns)
9640                if let Some(ref clustered) = modifiers.clustered {
9641                    self.write_space();
9642                    self.write_keyword(clustered);
9643                }
9644                // PostgreSQL 15+: NULLS NOT DISTINCT
9645                if *nulls_not_distinct {
9646                    self.write(" NULLS NOT DISTINCT");
9647                }
9648                // MySQL format: UNIQUE name (cols) when no CONSTRAINT keyword
9649                if let Some(ref n) = name {
9650                    if !*has_constraint_keyword {
9651                        self.write_space();
9652                        self.generate_identifier(n)?;
9653                    }
9654                }
9655                if *columns_parenthesized {
9656                    self.write(" (");
9657                    for (i, col) in columns.iter().enumerate() {
9658                        if i > 0 {
9659                            self.write(", ");
9660                        }
9661                        self.generate_identifier(col)?;
9662                    }
9663                    self.write(")");
9664                } else {
9665                    // UNIQUE without parentheses (e.g., UNIQUE idx_name)
9666                    for col in columns.iter() {
9667                        self.write_space();
9668                        self.generate_identifier(col)?;
9669                    }
9670                }
9671                self.generate_constraint_modifiers(modifiers);
9672            }
9673            TableConstraint::ForeignKey {
9674                name,
9675                columns,
9676                references,
9677                on_delete,
9678                on_update,
9679                modifiers,
9680            } => {
9681                if let Some(ref n) = name {
9682                    self.write_keyword("CONSTRAINT");
9683                    self.write_space();
9684                    self.generate_identifier(n)?;
9685                    self.write_space();
9686                }
9687                self.write_keyword("FOREIGN KEY");
9688                self.write(" (");
9689                for (i, col) in columns.iter().enumerate() {
9690                    if i > 0 {
9691                        self.write(", ");
9692                    }
9693                    self.generate_identifier(col)?;
9694                }
9695                self.write(")");
9696                if let Some(ref refs) = references {
9697                    self.write(" ");
9698                    self.write_keyword("REFERENCES");
9699                    self.write_space();
9700                    self.generate_table(&refs.table)?;
9701                    if !refs.columns.is_empty() {
9702                        if self.config.pretty {
9703                            self.write(" (");
9704                            self.write_newline();
9705                            self.indent_level += 1;
9706                            for (i, col) in refs.columns.iter().enumerate() {
9707                                if i > 0 {
9708                                    self.write(",");
9709                                    self.write_newline();
9710                                }
9711                                self.write_indent();
9712                                self.generate_identifier(col)?;
9713                            }
9714                            self.indent_level -= 1;
9715                            self.write_newline();
9716                            self.write_indent();
9717                            self.write(")");
9718                        } else {
9719                            self.write(" (");
9720                            for (i, col) in refs.columns.iter().enumerate() {
9721                                if i > 0 {
9722                                    self.write(", ");
9723                                }
9724                                self.generate_identifier(col)?;
9725                            }
9726                            self.write(")");
9727                        }
9728                    }
9729                    self.generate_referential_actions(refs)?;
9730                } else {
9731                    // No REFERENCES - output ON DELETE/ON UPDATE directly
9732                    if let Some(ref action) = on_delete {
9733                        self.write_space();
9734                        self.write_keyword("ON DELETE");
9735                        self.write_space();
9736                        self.generate_referential_action(action);
9737                    }
9738                    if let Some(ref action) = on_update {
9739                        self.write_space();
9740                        self.write_keyword("ON UPDATE");
9741                        self.write_space();
9742                        self.generate_referential_action(action);
9743                    }
9744                }
9745                self.generate_constraint_modifiers(modifiers);
9746            }
9747            TableConstraint::Check {
9748                name,
9749                expression,
9750                modifiers,
9751            } => {
9752                if let Some(ref n) = name {
9753                    self.write_keyword("CONSTRAINT");
9754                    self.write_space();
9755                    self.generate_identifier(n)?;
9756                    self.write_space();
9757                }
9758                self.write_keyword("CHECK");
9759                self.write(" (");
9760                self.generate_expression(expression)?;
9761                self.write(")");
9762                self.generate_constraint_modifiers(modifiers);
9763            }
9764            TableConstraint::Assume { name, expression } => {
9765                if let Some(ref n) = name {
9766                    self.write_keyword("CONSTRAINT");
9767                    self.write_space();
9768                    self.generate_identifier(n)?;
9769                    self.write_space();
9770                }
9771                self.write_keyword("ASSUME");
9772                self.write(" (");
9773                self.generate_expression(expression)?;
9774                self.write(")");
9775            }
9776            TableConstraint::Default {
9777                name,
9778                expression,
9779                column,
9780            } => {
9781                if let Some(ref n) = name {
9782                    self.write_keyword("CONSTRAINT");
9783                    self.write_space();
9784                    self.generate_identifier(n)?;
9785                    self.write_space();
9786                }
9787                self.write_keyword("DEFAULT");
9788                self.write_space();
9789                self.generate_expression(expression)?;
9790                self.write_space();
9791                self.write_keyword("FOR");
9792                self.write_space();
9793                self.generate_identifier(column)?;
9794            }
9795            TableConstraint::Index {
9796                name,
9797                columns,
9798                kind,
9799                modifiers,
9800                use_key_keyword,
9801                expression,
9802                index_type,
9803                granularity,
9804            } => {
9805                // ClickHouse-style INDEX: INDEX name expr TYPE type_func GRANULARITY n
9806                if expression.is_some() {
9807                    self.write_keyword("INDEX");
9808                    if let Some(ref n) = name {
9809                        self.write_space();
9810                        self.generate_identifier(n)?;
9811                    }
9812                    if let Some(ref expr) = expression {
9813                        self.write_space();
9814                        self.generate_expression(expr)?;
9815                    }
9816                    if let Some(ref idx_type) = index_type {
9817                        self.write_space();
9818                        self.write_keyword("TYPE");
9819                        self.write_space();
9820                        self.generate_expression(idx_type)?;
9821                    }
9822                    if let Some(ref gran) = granularity {
9823                        self.write_space();
9824                        self.write_keyword("GRANULARITY");
9825                        self.write_space();
9826                        self.generate_expression(gran)?;
9827                    }
9828                } else {
9829                    // Standard INDEX syntax
9830                    // Determine the index keyword to use
9831                    // MySQL normalizes KEY to INDEX
9832                    use crate::dialects::DialectType;
9833                    let index_keyword = if *use_key_keyword
9834                        && !matches!(self.config.dialect, Some(DialectType::MySQL))
9835                    {
9836                        "KEY"
9837                    } else {
9838                        "INDEX"
9839                    };
9840
9841                    // Output kind (UNIQUE, FULLTEXT, SPATIAL) if present
9842                    if let Some(ref k) = kind {
9843                        self.write_keyword(k);
9844                        // For UNIQUE, don't add INDEX/KEY keyword
9845                        if k != "UNIQUE" {
9846                            self.write_space();
9847                            self.write_keyword(index_keyword);
9848                        }
9849                    } else {
9850                        self.write_keyword(index_keyword);
9851                    }
9852
9853                    // Output USING before name if using_before_columns is true and there's no name
9854                    if modifiers.using_before_columns && name.is_none() {
9855                        if let Some(ref using) = modifiers.using {
9856                            self.write_space();
9857                            self.write_keyword("USING");
9858                            self.write_space();
9859                            self.write_keyword(using);
9860                        }
9861                    }
9862
9863                    // Output index name if present
9864                    if let Some(ref n) = name {
9865                        self.write_space();
9866                        self.generate_identifier(n)?;
9867                    }
9868
9869                    // Output USING after name but before columns if using_before_columns and there's a name
9870                    if modifiers.using_before_columns && name.is_some() {
9871                        if let Some(ref using) = modifiers.using {
9872                            self.write_space();
9873                            self.write_keyword("USING");
9874                            self.write_space();
9875                            self.write_keyword(using);
9876                        }
9877                    }
9878
9879                    // Output columns
9880                    self.write(" (");
9881                    for (i, col) in columns.iter().enumerate() {
9882                        if i > 0 {
9883                            self.write(", ");
9884                        }
9885                        self.generate_identifier(col)?;
9886                    }
9887                    self.write(")");
9888
9889                    // Output USING after columns if not using_before_columns
9890                    if !modifiers.using_before_columns {
9891                        if let Some(ref using) = modifiers.using {
9892                            self.write_space();
9893                            self.write_keyword("USING");
9894                            self.write_space();
9895                            self.write_keyword(using);
9896                        }
9897                    }
9898
9899                    // Output other constraint modifiers (but skip USING since we already handled it)
9900                    self.generate_constraint_modifiers_without_using(modifiers);
9901                }
9902            }
9903            TableConstraint::Projection { name, expression } => {
9904                // ClickHouse: PROJECTION name (SELECT ...)
9905                self.write_keyword("PROJECTION");
9906                self.write_space();
9907                self.generate_identifier(name)?;
9908                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
9909                    if let Expression::Raw(raw) = expression {
9910                        if raw
9911                            .sql
9912                            .trim_start()
9913                            .to_ascii_uppercase()
9914                            .starts_with("INDEX ")
9915                        {
9916                            self.write_space();
9917                            self.write(raw.sql.trim());
9918                            return Ok(());
9919                        }
9920                    }
9921                }
9922                self.write(" (");
9923                self.generate_expression(expression)?;
9924                self.write(")");
9925            }
9926            TableConstraint::Like { source, options } => {
9927                self.write_keyword("LIKE");
9928                self.write_space();
9929                self.generate_table(source)?;
9930                for (action, prop) in options {
9931                    self.write_space();
9932                    match action {
9933                        LikeOptionAction::Including => self.write_keyword("INCLUDING"),
9934                        LikeOptionAction::Excluding => self.write_keyword("EXCLUDING"),
9935                    }
9936                    self.write_space();
9937                    self.write_keyword(prop);
9938                }
9939            }
9940            TableConstraint::PeriodForSystemTime { start_col, end_col } => {
9941                self.write_keyword("PERIOD FOR SYSTEM_TIME");
9942                self.write(" (");
9943                self.generate_identifier(start_col)?;
9944                self.write(", ");
9945                self.generate_identifier(end_col)?;
9946                self.write(")");
9947            }
9948            TableConstraint::Exclude {
9949                name,
9950                using,
9951                elements,
9952                include_columns,
9953                where_clause,
9954                with_params,
9955                using_index_tablespace,
9956                modifiers: _,
9957            } => {
9958                if let Some(ref n) = name {
9959                    self.write_keyword("CONSTRAINT");
9960                    self.write_space();
9961                    self.generate_identifier(n)?;
9962                    self.write_space();
9963                }
9964                self.write_keyword("EXCLUDE");
9965                if let Some(ref method) = using {
9966                    self.write_space();
9967                    self.write_keyword("USING");
9968                    self.write_space();
9969                    self.write(method);
9970                    self.write("(");
9971                } else {
9972                    self.write(" (");
9973                }
9974                for (i, elem) in elements.iter().enumerate() {
9975                    if i > 0 {
9976                        self.write(", ");
9977                    }
9978                    self.write(&elem.expression);
9979                    self.write_space();
9980                    self.write_keyword("WITH");
9981                    self.write_space();
9982                    self.write(&elem.operator);
9983                }
9984                self.write(")");
9985                if !include_columns.is_empty() {
9986                    self.write_space();
9987                    self.write_keyword("INCLUDE");
9988                    self.write(" (");
9989                    for (i, col) in include_columns.iter().enumerate() {
9990                        if i > 0 {
9991                            self.write(", ");
9992                        }
9993                        self.generate_identifier(col)?;
9994                    }
9995                    self.write(")");
9996                }
9997                if !with_params.is_empty() {
9998                    self.write_space();
9999                    self.write_keyword("WITH");
10000                    self.write(" (");
10001                    for (i, (key, val)) in with_params.iter().enumerate() {
10002                        if i > 0 {
10003                            self.write(", ");
10004                        }
10005                        self.write(key);
10006                        self.write("=");
10007                        self.write(val);
10008                    }
10009                    self.write(")");
10010                }
10011                if let Some(ref tablespace) = using_index_tablespace {
10012                    self.write_space();
10013                    self.write_keyword("USING INDEX TABLESPACE");
10014                    self.write_space();
10015                    self.write(tablespace);
10016                }
10017                if let Some(ref where_expr) = where_clause {
10018                    self.write_space();
10019                    self.write_keyword("WHERE");
10020                    self.write(" (");
10021                    self.generate_expression(where_expr)?;
10022                    self.write(")");
10023                }
10024            }
10025            TableConstraint::Tags(tags) => {
10026                self.write_keyword("TAG");
10027                self.write(" (");
10028                for (i, expr) in tags.expressions.iter().enumerate() {
10029                    if i > 0 {
10030                        self.write(", ");
10031                    }
10032                    self.generate_expression(expr)?;
10033                }
10034                self.write(")");
10035            }
10036            TableConstraint::InitiallyDeferred { deferred } => {
10037                self.write_keyword("INITIALLY");
10038                self.write_space();
10039                if *deferred {
10040                    self.write_keyword("DEFERRED");
10041                } else {
10042                    self.write_keyword("IMMEDIATE");
10043                }
10044            }
10045        }
10046        Ok(())
10047    }
10048
10049    fn generate_constraint_modifiers(&mut self, modifiers: &ConstraintModifiers) {
10050        // Output USING BTREE/HASH (MySQL) - comes first
10051        if let Some(using) = &modifiers.using {
10052            self.write_space();
10053            self.write_keyword("USING");
10054            self.write_space();
10055            self.write_keyword(using);
10056        }
10057        // Output ENFORCED/NOT ENFORCED
10058        if let Some(enforced) = modifiers.enforced {
10059            self.write_space();
10060            if enforced {
10061                self.write_keyword("ENFORCED");
10062            } else {
10063                self.write_keyword("NOT ENFORCED");
10064            }
10065        }
10066        // Output DEFERRABLE/NOT DEFERRABLE
10067        if let Some(deferrable) = modifiers.deferrable {
10068            self.write_space();
10069            if deferrable {
10070                self.write_keyword("DEFERRABLE");
10071            } else {
10072                self.write_keyword("NOT DEFERRABLE");
10073            }
10074        }
10075        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
10076        if let Some(initially_deferred) = modifiers.initially_deferred {
10077            self.write_space();
10078            if initially_deferred {
10079                self.write_keyword("INITIALLY DEFERRED");
10080            } else {
10081                self.write_keyword("INITIALLY IMMEDIATE");
10082            }
10083        }
10084        // Output NORELY
10085        if modifiers.norely {
10086            self.write_space();
10087            self.write_keyword("NORELY");
10088        }
10089        // Output RELY
10090        if modifiers.rely {
10091            self.write_space();
10092            self.write_keyword("RELY");
10093        }
10094        // Output NOT VALID (PostgreSQL)
10095        if modifiers.not_valid {
10096            self.write_space();
10097            self.write_keyword("NOT VALID");
10098        }
10099        // Output ON CONFLICT (SQLite)
10100        if let Some(on_conflict) = &modifiers.on_conflict {
10101            self.write_space();
10102            self.write_keyword("ON CONFLICT");
10103            self.write_space();
10104            self.write_keyword(on_conflict);
10105        }
10106        // Output TSQL WITH options (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF, ...)
10107        if !modifiers.with_options.is_empty() {
10108            self.write_space();
10109            self.write_keyword("WITH");
10110            self.write(" (");
10111            for (i, (key, value)) in modifiers.with_options.iter().enumerate() {
10112                if i > 0 {
10113                    self.write(", ");
10114                }
10115                self.write(key);
10116                self.write("=");
10117                self.write(value);
10118            }
10119            self.write(")");
10120        }
10121        // Output TSQL ON filegroup
10122        if let Some(ref fg) = modifiers.on_filegroup {
10123            self.write_space();
10124            self.write_keyword("ON");
10125            self.write_space();
10126            let _ = self.generate_identifier(fg);
10127        }
10128    }
10129
10130    /// Generate constraint modifiers without USING (for Index constraints where USING is handled separately)
10131    fn generate_constraint_modifiers_without_using(&mut self, modifiers: &ConstraintModifiers) {
10132        // Output ENFORCED/NOT ENFORCED
10133        if let Some(enforced) = modifiers.enforced {
10134            self.write_space();
10135            if enforced {
10136                self.write_keyword("ENFORCED");
10137            } else {
10138                self.write_keyword("NOT ENFORCED");
10139            }
10140        }
10141        // Output DEFERRABLE/NOT DEFERRABLE
10142        if let Some(deferrable) = modifiers.deferrable {
10143            self.write_space();
10144            if deferrable {
10145                self.write_keyword("DEFERRABLE");
10146            } else {
10147                self.write_keyword("NOT DEFERRABLE");
10148            }
10149        }
10150        // Output INITIALLY DEFERRED/INITIALLY IMMEDIATE
10151        if let Some(initially_deferred) = modifiers.initially_deferred {
10152            self.write_space();
10153            if initially_deferred {
10154                self.write_keyword("INITIALLY DEFERRED");
10155            } else {
10156                self.write_keyword("INITIALLY IMMEDIATE");
10157            }
10158        }
10159        // Output NORELY
10160        if modifiers.norely {
10161            self.write_space();
10162            self.write_keyword("NORELY");
10163        }
10164        // Output RELY
10165        if modifiers.rely {
10166            self.write_space();
10167            self.write_keyword("RELY");
10168        }
10169        // Output NOT VALID (PostgreSQL)
10170        if modifiers.not_valid {
10171            self.write_space();
10172            self.write_keyword("NOT VALID");
10173        }
10174        // Output ON CONFLICT (SQLite)
10175        if let Some(on_conflict) = &modifiers.on_conflict {
10176            self.write_space();
10177            self.write_keyword("ON CONFLICT");
10178            self.write_space();
10179            self.write_keyword(on_conflict);
10180        }
10181        // Output MySQL index-specific modifiers
10182        self.generate_index_specific_modifiers(modifiers);
10183    }
10184
10185    /// Generate MySQL index-specific modifiers (COMMENT, VISIBLE, ENGINE_ATTRIBUTE, WITH PARSER)
10186    fn generate_index_specific_modifiers(&mut self, modifiers: &ConstraintModifiers) {
10187        if let Some(ref comment) = modifiers.comment {
10188            self.write_space();
10189            self.write_keyword("COMMENT");
10190            self.write(" '");
10191            self.write(comment);
10192            self.write("'");
10193        }
10194        if let Some(visible) = modifiers.visible {
10195            self.write_space();
10196            if visible {
10197                self.write_keyword("VISIBLE");
10198            } else {
10199                self.write_keyword("INVISIBLE");
10200            }
10201        }
10202        if let Some(ref attr) = modifiers.engine_attribute {
10203            self.write_space();
10204            self.write_keyword("ENGINE_ATTRIBUTE");
10205            self.write(" = '");
10206            self.write(attr);
10207            self.write("'");
10208        }
10209        if let Some(ref parser) = modifiers.with_parser {
10210            self.write_space();
10211            self.write_keyword("WITH PARSER");
10212            self.write_space();
10213            self.write(parser);
10214        }
10215    }
10216
10217    fn generate_referential_actions(&mut self, fk_ref: &ForeignKeyRef) -> Result<()> {
10218        // MATCH clause before ON DELETE/ON UPDATE (default position, e.g. PostgreSQL)
10219        if !fk_ref.match_after_actions {
10220            if let Some(ref match_type) = fk_ref.match_type {
10221                self.write_space();
10222                self.write_keyword("MATCH");
10223                self.write_space();
10224                match match_type {
10225                    MatchType::Full => self.write_keyword("FULL"),
10226                    MatchType::Partial => self.write_keyword("PARTIAL"),
10227                    MatchType::Simple => self.write_keyword("SIMPLE"),
10228                }
10229            }
10230        }
10231
10232        // Output ON UPDATE and ON DELETE in the original order
10233        if fk_ref.on_update_first {
10234            if let Some(ref action) = fk_ref.on_update {
10235                self.write_space();
10236                self.write_keyword("ON UPDATE");
10237                self.write_space();
10238                self.generate_referential_action(action);
10239            }
10240            if let Some(ref action) = fk_ref.on_delete {
10241                self.write_space();
10242                self.write_keyword("ON DELETE");
10243                self.write_space();
10244                self.generate_referential_action(action);
10245            }
10246        } else {
10247            if let Some(ref action) = fk_ref.on_delete {
10248                self.write_space();
10249                self.write_keyword("ON DELETE");
10250                self.write_space();
10251                self.generate_referential_action(action);
10252            }
10253            if let Some(ref action) = fk_ref.on_update {
10254                self.write_space();
10255                self.write_keyword("ON UPDATE");
10256                self.write_space();
10257                self.generate_referential_action(action);
10258            }
10259        }
10260
10261        // MATCH clause after ON DELETE/ON UPDATE (when original SQL had it after)
10262        if fk_ref.match_after_actions {
10263            if let Some(ref match_type) = fk_ref.match_type {
10264                self.write_space();
10265                self.write_keyword("MATCH");
10266                self.write_space();
10267                match match_type {
10268                    MatchType::Full => self.write_keyword("FULL"),
10269                    MatchType::Partial => self.write_keyword("PARTIAL"),
10270                    MatchType::Simple => self.write_keyword("SIMPLE"),
10271                }
10272            }
10273        }
10274
10275        // DEFERRABLE / NOT DEFERRABLE
10276        if let Some(deferrable) = fk_ref.deferrable {
10277            self.write_space();
10278            if deferrable {
10279                self.write_keyword("DEFERRABLE");
10280            } else {
10281                self.write_keyword("NOT DEFERRABLE");
10282            }
10283        }
10284
10285        Ok(())
10286    }
10287
10288    fn generate_referential_action(&mut self, action: &ReferentialAction) {
10289        match action {
10290            ReferentialAction::Cascade => self.write_keyword("CASCADE"),
10291            ReferentialAction::SetNull => self.write_keyword("SET NULL"),
10292            ReferentialAction::SetDefault => self.write_keyword("SET DEFAULT"),
10293            ReferentialAction::Restrict => self.write_keyword("RESTRICT"),
10294            ReferentialAction::NoAction => self.write_keyword("NO ACTION"),
10295        }
10296    }
10297
10298    fn generate_drop_table(&mut self, dt: &DropTable) -> Result<()> {
10299        // TSQL: IF NOT OBJECT_ID(...) IS NULL BEGIN DROP TABLE ...; END
10300        if let Some(ref object_id_args) = dt.object_id_args {
10301            if matches!(
10302                self.config.dialect,
10303                Some(crate::dialects::DialectType::TSQL)
10304                    | Some(crate::dialects::DialectType::Fabric)
10305            ) {
10306                self.write_keyword("IF NOT OBJECT_ID");
10307                self.write("(");
10308                self.write(object_id_args);
10309                self.write(")");
10310                self.write_space();
10311                self.write_keyword("IS NULL BEGIN DROP TABLE");
10312                self.write_space();
10313                for (i, table) in dt.names.iter().enumerate() {
10314                    if i > 0 {
10315                        self.write(", ");
10316                    }
10317                    self.generate_table(table)?;
10318                }
10319                self.write("; ");
10320                self.write_keyword("END");
10321                return Ok(());
10322            }
10323        }
10324
10325        // Athena: DROP TABLE uses Hive engine (backticks)
10326        let saved_athena_hive_context = self.athena_hive_context;
10327        if matches!(
10328            self.config.dialect,
10329            Some(crate::dialects::DialectType::Athena)
10330        ) {
10331            self.athena_hive_context = true;
10332        }
10333
10334        // Output leading comments (e.g., "-- comment\nDROP TABLE ...")
10335        for comment in &dt.leading_comments {
10336            self.write_formatted_comment(comment);
10337            self.write_space();
10338        }
10339        if dt.iceberg {
10340            self.write_keyword("DROP ICEBERG TABLE");
10341        } else {
10342            self.write_keyword("DROP TABLE");
10343        }
10344
10345        if dt.if_exists {
10346            self.write_space();
10347            self.write_keyword("IF EXISTS");
10348        }
10349
10350        self.write_space();
10351        for (i, table) in dt.names.iter().enumerate() {
10352            if i > 0 {
10353                self.write(", ");
10354            }
10355            self.generate_table(table)?;
10356        }
10357
10358        if dt.cascade_constraints {
10359            self.write_space();
10360            self.write_keyword("CASCADE CONSTRAINTS");
10361        } else if dt.cascade {
10362            self.write_space();
10363            self.write_keyword("CASCADE");
10364        }
10365
10366        if dt.restrict {
10367            self.write_space();
10368            self.write_keyword("RESTRICT");
10369        }
10370
10371        if dt.purge {
10372            self.write_space();
10373            self.write_keyword("PURGE");
10374        }
10375
10376        if dt.sync {
10377            self.write_space();
10378            self.write_keyword("SYNC");
10379        }
10380
10381        // Restore Athena Hive context
10382        self.athena_hive_context = saved_athena_hive_context;
10383
10384        Ok(())
10385    }
10386
10387    fn generate_undrop(&mut self, u: &Undrop) -> Result<()> {
10388        self.write_keyword("UNDROP");
10389        self.write_space();
10390        self.write_keyword(&u.kind);
10391        if u.if_exists {
10392            self.write_space();
10393            self.write_keyword("IF EXISTS");
10394        }
10395        self.write_space();
10396        self.generate_table(&u.name)?;
10397        Ok(())
10398    }
10399
10400    fn generate_alter_table(&mut self, at: &AlterTable) -> Result<()> {
10401        // Athena: ALTER TABLE uses Hive engine (backticks)
10402        let saved_athena_hive_context = self.athena_hive_context;
10403        if matches!(
10404            self.config.dialect,
10405            Some(crate::dialects::DialectType::Athena)
10406        ) {
10407            self.athena_hive_context = true;
10408        }
10409
10410        self.write_keyword("ALTER");
10411        // Write table modifier (e.g., ICEBERG) unless target is DuckDB
10412        if let Some(ref modifier) = at.table_modifier {
10413            if !matches!(
10414                self.config.dialect,
10415                Some(crate::dialects::DialectType::DuckDB)
10416            ) {
10417                self.write_space();
10418                self.write_keyword(modifier);
10419            }
10420        }
10421        self.write(" ");
10422        self.write_keyword("TABLE");
10423        if at.if_exists {
10424            self.write_space();
10425            self.write_keyword("IF EXISTS");
10426        }
10427        self.write_space();
10428        self.generate_table(&at.name)?;
10429
10430        // ClickHouse: ON CLUSTER clause
10431        if let Some(ref on_cluster) = at.on_cluster {
10432            self.write_space();
10433            self.generate_on_cluster(on_cluster)?;
10434        }
10435
10436        // Hive: PARTITION(key=value, ...) clause
10437        if let Some(ref partition) = at.partition {
10438            self.write_space();
10439            self.write_keyword("PARTITION");
10440            self.write("(");
10441            for (i, (key, value)) in partition.iter().enumerate() {
10442                if i > 0 {
10443                    self.write(", ");
10444                }
10445                self.generate_identifier(key)?;
10446                self.write(" = ");
10447                self.generate_expression(value)?;
10448            }
10449            self.write(")");
10450        }
10451
10452        // TSQL: WITH CHECK / WITH NOCHECK modifier
10453        if let Some(ref with_check) = at.with_check {
10454            self.write_space();
10455            self.write_keyword(with_check);
10456        }
10457
10458        if self.config.pretty {
10459            // In pretty mode, format actions with newlines and indentation
10460            self.write_newline();
10461            self.indent_level += 1;
10462            for (i, action) in at.actions.iter().enumerate() {
10463                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10464                let is_continuation = i > 0
10465                    && matches!(
10466                        (&at.actions[i - 1], action),
10467                        (
10468                            AlterTableAction::AddColumn { .. },
10469                            AlterTableAction::AddColumn { .. }
10470                        ) | (
10471                            AlterTableAction::AddConstraint(_),
10472                            AlterTableAction::AddConstraint(_)
10473                        )
10474                    );
10475                if i > 0 {
10476                    self.write(",");
10477                    self.write_newline();
10478                }
10479                self.write_indent();
10480                self.generate_alter_action_with_continuation(action, is_continuation)?;
10481            }
10482            self.indent_level -= 1;
10483        } else {
10484            for (i, action) in at.actions.iter().enumerate() {
10485                // Check if this is a continuation of previous ADD COLUMN or ADD CONSTRAINT
10486                let is_continuation = i > 0
10487                    && matches!(
10488                        (&at.actions[i - 1], action),
10489                        (
10490                            AlterTableAction::AddColumn { .. },
10491                            AlterTableAction::AddColumn { .. }
10492                        ) | (
10493                            AlterTableAction::AddConstraint(_),
10494                            AlterTableAction::AddConstraint(_)
10495                        )
10496                    );
10497                if i > 0 {
10498                    self.write(",");
10499                }
10500                self.write_space();
10501                self.generate_alter_action_with_continuation(action, is_continuation)?;
10502            }
10503        }
10504
10505        // MySQL ALTER TABLE trailing options
10506        if let Some(ref algorithm) = at.algorithm {
10507            self.write(", ");
10508            self.write_keyword("ALGORITHM");
10509            self.write("=");
10510            self.write_keyword(algorithm);
10511        }
10512        if let Some(ref lock) = at.lock {
10513            self.write(", ");
10514            self.write_keyword("LOCK");
10515            self.write("=");
10516            self.write_keyword(lock);
10517        }
10518
10519        // Restore Athena Hive context
10520        self.athena_hive_context = saved_athena_hive_context;
10521
10522        Ok(())
10523    }
10524
10525    fn generate_alter_action_with_continuation(
10526        &mut self,
10527        action: &AlterTableAction,
10528        is_continuation: bool,
10529    ) -> Result<()> {
10530        match action {
10531            AlterTableAction::AddColumn {
10532                column,
10533                if_not_exists,
10534                position,
10535            } => {
10536                use crate::dialects::DialectType;
10537                // For Snowflake: consecutive ADD COLUMN actions are combined with commas
10538                // e.g., "ADD col1, col2" instead of "ADD col1, ADD col2"
10539                // For other dialects, repeat ADD COLUMN for each
10540                let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
10541                let is_tsql_like = matches!(
10542                    self.config.dialect,
10543                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
10544                );
10545                // Athena uses "ADD COLUMNS (col_def)" instead of "ADD COLUMN col_def"
10546                let is_athena = matches!(self.config.dialect, Some(DialectType::Athena));
10547
10548                if is_continuation && (is_snowflake || is_tsql_like) {
10549                    // Don't write ADD keyword for continuation in Snowflake/TSQL
10550                } else if is_snowflake {
10551                    self.write_keyword("ADD");
10552                    self.write_space();
10553                } else if is_athena {
10554                    // Athena uses ADD COLUMNS (col_def) syntax
10555                    self.write_keyword("ADD COLUMNS");
10556                    self.write(" (");
10557                } else if self.config.alter_table_include_column_keyword {
10558                    self.write_keyword("ADD COLUMN");
10559                    self.write_space();
10560                } else {
10561                    // Dialects like Oracle and TSQL don't use COLUMN keyword
10562                    self.write_keyword("ADD");
10563                    self.write_space();
10564                }
10565
10566                if *if_not_exists {
10567                    self.write_keyword("IF NOT EXISTS");
10568                    self.write_space();
10569                }
10570                self.generate_column_def(column)?;
10571
10572                // Close parenthesis for Athena
10573                if is_athena {
10574                    self.write(")");
10575                }
10576
10577                // Column position (FIRST or AFTER)
10578                if let Some(pos) = position {
10579                    self.write_space();
10580                    match pos {
10581                        ColumnPosition::First => self.write_keyword("FIRST"),
10582                        ColumnPosition::After(col_name) => {
10583                            self.write_keyword("AFTER");
10584                            self.write_space();
10585                            self.generate_identifier(col_name)?;
10586                        }
10587                    }
10588                }
10589            }
10590            AlterTableAction::DropColumn {
10591                name,
10592                if_exists,
10593                cascade,
10594            } => {
10595                self.write_keyword("DROP COLUMN");
10596                if *if_exists {
10597                    self.write_space();
10598                    self.write_keyword("IF EXISTS");
10599                }
10600                self.write_space();
10601                self.generate_identifier(name)?;
10602                if *cascade {
10603                    self.write_space();
10604                    self.write_keyword("CASCADE");
10605                }
10606            }
10607            AlterTableAction::DropColumns { names } => {
10608                self.write_keyword("DROP COLUMNS");
10609                self.write(" (");
10610                for (i, name) in names.iter().enumerate() {
10611                    if i > 0 {
10612                        self.write(", ");
10613                    }
10614                    self.generate_identifier(name)?;
10615                }
10616                self.write(")");
10617            }
10618            AlterTableAction::RenameColumn {
10619                old_name,
10620                new_name,
10621                if_exists,
10622            } => {
10623                self.write_keyword("RENAME COLUMN");
10624                if *if_exists {
10625                    self.write_space();
10626                    self.write_keyword("IF EXISTS");
10627                }
10628                self.write_space();
10629                self.generate_identifier(old_name)?;
10630                self.write_space();
10631                self.write_keyword("TO");
10632                self.write_space();
10633                self.generate_identifier(new_name)?;
10634            }
10635            AlterTableAction::AlterColumn {
10636                name,
10637                action,
10638                use_modify_keyword,
10639            } => {
10640                use crate::dialects::DialectType;
10641                // MySQL uses MODIFY COLUMN for type changes (SetDataType)
10642                // but ALTER COLUMN for SET DEFAULT, DROP DEFAULT, etc.
10643                let use_modify = *use_modify_keyword
10644                    || (matches!(self.config.dialect, Some(DialectType::MySQL))
10645                        && matches!(action, AlterColumnAction::SetDataType { .. }));
10646                if use_modify {
10647                    self.write_keyword("MODIFY COLUMN");
10648                    self.write_space();
10649                    self.generate_identifier(name)?;
10650                    // For MODIFY COLUMN, output the type directly
10651                    if let AlterColumnAction::SetDataType {
10652                        data_type,
10653                        using: _,
10654                        collate,
10655                    } = action
10656                    {
10657                        self.write_space();
10658                        self.generate_data_type(data_type)?;
10659                        // Output COLLATE clause if present
10660                        if let Some(collate_name) = collate {
10661                            self.write_space();
10662                            self.write_keyword("COLLATE");
10663                            self.write_space();
10664                            // Output as single-quoted string
10665                            self.write(&format!("'{}'", collate_name));
10666                        }
10667                    } else {
10668                        self.write_space();
10669                        self.generate_alter_column_action(action)?;
10670                    }
10671                } else if matches!(self.config.dialect, Some(DialectType::Hive))
10672                    && matches!(action, AlterColumnAction::SetDataType { .. })
10673                {
10674                    // Hive uses CHANGE COLUMN col_name col_name NEW_TYPE
10675                    self.write_keyword("CHANGE COLUMN");
10676                    self.write_space();
10677                    self.generate_identifier(name)?;
10678                    self.write_space();
10679                    self.generate_identifier(name)?;
10680                    if let AlterColumnAction::SetDataType { data_type, .. } = action {
10681                        self.write_space();
10682                        self.generate_data_type(data_type)?;
10683                    }
10684                } else {
10685                    self.write_keyword("ALTER COLUMN");
10686                    self.write_space();
10687                    self.generate_identifier(name)?;
10688                    self.write_space();
10689                    self.generate_alter_column_action(action)?;
10690                }
10691            }
10692            AlterTableAction::RenameTable(new_name) => {
10693                // MySQL-like dialects (MySQL, Doris, StarRocks) use RENAME without TO
10694                let mysql_like = matches!(
10695                    self.config.dialect,
10696                    Some(DialectType::MySQL)
10697                        | Some(DialectType::Doris)
10698                        | Some(DialectType::StarRocks)
10699                        | Some(DialectType::SingleStore)
10700                );
10701                if mysql_like {
10702                    self.write_keyword("RENAME");
10703                } else {
10704                    self.write_keyword("RENAME TO");
10705                }
10706                self.write_space();
10707                // Doris, DuckDB, BigQuery, PostgreSQL strip schema/catalog from target table
10708                let rename_table_with_db = !matches!(
10709                    self.config.dialect,
10710                    Some(DialectType::Doris)
10711                        | Some(DialectType::DuckDB)
10712                        | Some(DialectType::BigQuery)
10713                        | Some(DialectType::PostgreSQL)
10714                );
10715                if !rename_table_with_db {
10716                    let mut stripped = new_name.clone();
10717                    stripped.schema = None;
10718                    stripped.catalog = None;
10719                    self.generate_table(&stripped)?;
10720                } else {
10721                    self.generate_table(new_name)?;
10722                }
10723            }
10724            AlterTableAction::AddConstraint(constraint) => {
10725                // For consecutive ADD CONSTRAINT actions (is_continuation=true), skip ADD keyword
10726                // to produce: ADD CONSTRAINT c1 ..., CONSTRAINT c2 ...
10727                if !is_continuation {
10728                    self.write_keyword("ADD");
10729                    self.write_space();
10730                }
10731                self.generate_table_constraint(constraint)?;
10732            }
10733            AlterTableAction::DropConstraint { name, if_exists } => {
10734                self.write_keyword("DROP CONSTRAINT");
10735                if *if_exists {
10736                    self.write_space();
10737                    self.write_keyword("IF EXISTS");
10738                }
10739                self.write_space();
10740                self.generate_identifier(name)?;
10741            }
10742            AlterTableAction::DropForeignKey { name } => {
10743                self.write_keyword("DROP FOREIGN KEY");
10744                self.write_space();
10745                self.generate_identifier(name)?;
10746            }
10747            AlterTableAction::DropPartition {
10748                partitions,
10749                if_exists,
10750            } => {
10751                self.write_keyword("DROP");
10752                if *if_exists {
10753                    self.write_space();
10754                    self.write_keyword("IF EXISTS");
10755                }
10756                for (i, partition) in partitions.iter().enumerate() {
10757                    if i > 0 {
10758                        self.write(",");
10759                    }
10760                    self.write_space();
10761                    self.write_keyword("PARTITION");
10762                    // Check for special ClickHouse partition formats
10763                    if partition.len() == 1 && partition[0].0.name == "__expr__" {
10764                        // ClickHouse: PARTITION <expression>
10765                        self.write_space();
10766                        self.generate_expression(&partition[0].1)?;
10767                    } else if partition.len() == 1 && partition[0].0.name == "ALL" {
10768                        // ClickHouse: PARTITION ALL
10769                        self.write_space();
10770                        self.write_keyword("ALL");
10771                    } else if partition.len() == 1 && partition[0].0.name == "ID" {
10772                        // ClickHouse: PARTITION ID 'string'
10773                        self.write_space();
10774                        self.write_keyword("ID");
10775                        self.write_space();
10776                        self.generate_expression(&partition[0].1)?;
10777                    } else {
10778                        // Standard SQL: PARTITION(key=value, ...)
10779                        self.write("(");
10780                        for (j, (key, value)) in partition.iter().enumerate() {
10781                            if j > 0 {
10782                                self.write(", ");
10783                            }
10784                            self.generate_identifier(key)?;
10785                            self.write(" = ");
10786                            self.generate_expression(value)?;
10787                        }
10788                        self.write(")");
10789                    }
10790                }
10791            }
10792            AlterTableAction::Delete { where_clause } => {
10793                self.write_keyword("DELETE");
10794                self.write_space();
10795                self.write_keyword("WHERE");
10796                self.write_space();
10797                self.generate_expression(where_clause)?;
10798            }
10799            AlterTableAction::SwapWith(target) => {
10800                self.write_keyword("SWAP WITH");
10801                self.write_space();
10802                self.generate_table(target)?;
10803            }
10804            AlterTableAction::SetProperty { properties } => {
10805                use crate::dialects::DialectType;
10806                self.write_keyword("SET");
10807                // Trino/Presto use SET PROPERTIES syntax with spaces around =
10808                let is_trino_presto = matches!(
10809                    self.config.dialect,
10810                    Some(DialectType::Trino) | Some(DialectType::Presto)
10811                );
10812                if is_trino_presto {
10813                    self.write_space();
10814                    self.write_keyword("PROPERTIES");
10815                }
10816                let eq = if is_trino_presto { " = " } else { "=" };
10817                for (i, (key, value)) in properties.iter().enumerate() {
10818                    if i > 0 {
10819                        self.write(",");
10820                    }
10821                    self.write_space();
10822                    // Handle quoted property names for Trino
10823                    if key.contains(' ') {
10824                        self.generate_string_literal(key)?;
10825                    } else {
10826                        self.write(key);
10827                    }
10828                    self.write(eq);
10829                    self.generate_expression(value)?;
10830                }
10831            }
10832            AlterTableAction::UnsetProperty { properties } => {
10833                self.write_keyword("UNSET");
10834                for (i, name) in properties.iter().enumerate() {
10835                    if i > 0 {
10836                        self.write(",");
10837                    }
10838                    self.write_space();
10839                    self.write(name);
10840                }
10841            }
10842            AlterTableAction::ClusterBy { expressions } => {
10843                self.write_keyword("CLUSTER BY");
10844                self.write(" (");
10845                for (i, expr) in expressions.iter().enumerate() {
10846                    if i > 0 {
10847                        self.write(", ");
10848                    }
10849                    self.generate_expression(expr)?;
10850                }
10851                self.write(")");
10852            }
10853            AlterTableAction::SetTag { expressions } => {
10854                self.write_keyword("SET TAG");
10855                for (i, (key, value)) in expressions.iter().enumerate() {
10856                    if i > 0 {
10857                        self.write(",");
10858                    }
10859                    self.write_space();
10860                    self.write(key);
10861                    self.write(" = ");
10862                    self.generate_expression(value)?;
10863                }
10864            }
10865            AlterTableAction::UnsetTag { names } => {
10866                self.write_keyword("UNSET TAG");
10867                for (i, name) in names.iter().enumerate() {
10868                    if i > 0 {
10869                        self.write(",");
10870                    }
10871                    self.write_space();
10872                    self.write(name);
10873                }
10874            }
10875            AlterTableAction::SetOptions { expressions } => {
10876                self.write_keyword("SET");
10877                self.write(" (");
10878                for (i, expr) in expressions.iter().enumerate() {
10879                    if i > 0 {
10880                        self.write(", ");
10881                    }
10882                    self.generate_expression(expr)?;
10883                }
10884                self.write(")");
10885            }
10886            AlterTableAction::AlterIndex { name, visible } => {
10887                self.write_keyword("ALTER INDEX");
10888                self.write_space();
10889                self.generate_identifier(name)?;
10890                self.write_space();
10891                if *visible {
10892                    self.write_keyword("VISIBLE");
10893                } else {
10894                    self.write_keyword("INVISIBLE");
10895                }
10896            }
10897            AlterTableAction::SetAttribute { attribute } => {
10898                self.write_keyword("SET");
10899                self.write_space();
10900                self.write_keyword(attribute);
10901            }
10902            AlterTableAction::SetStageFileFormat { options } => {
10903                self.write_keyword("SET");
10904                self.write_space();
10905                self.write_keyword("STAGE_FILE_FORMAT");
10906                self.write(" = (");
10907                if let Some(opts) = options {
10908                    self.generate_space_separated_properties(opts)?;
10909                }
10910                self.write(")");
10911            }
10912            AlterTableAction::SetStageCopyOptions { options } => {
10913                self.write_keyword("SET");
10914                self.write_space();
10915                self.write_keyword("STAGE_COPY_OPTIONS");
10916                self.write(" = (");
10917                if let Some(opts) = options {
10918                    self.generate_space_separated_properties(opts)?;
10919                }
10920                self.write(")");
10921            }
10922            AlterTableAction::AddColumns { columns, cascade } => {
10923                // Oracle uses ADD (...) without COLUMNS keyword
10924                // Hive/Spark uses ADD COLUMNS (...)
10925                let is_oracle = matches!(self.config.dialect, Some(DialectType::Oracle));
10926                if is_oracle {
10927                    self.write_keyword("ADD");
10928                } else {
10929                    self.write_keyword("ADD COLUMNS");
10930                }
10931                self.write(" (");
10932                for (i, col) in columns.iter().enumerate() {
10933                    if i > 0 {
10934                        self.write(", ");
10935                    }
10936                    self.generate_column_def(col)?;
10937                }
10938                self.write(")");
10939                if *cascade {
10940                    self.write_space();
10941                    self.write_keyword("CASCADE");
10942                }
10943            }
10944            AlterTableAction::ChangeColumn {
10945                old_name,
10946                new_name,
10947                data_type,
10948                comment,
10949                cascade,
10950            } => {
10951                use crate::dialects::DialectType;
10952                let is_spark = matches!(
10953                    self.config.dialect,
10954                    Some(DialectType::Spark) | Some(DialectType::Databricks)
10955                );
10956                let is_rename = old_name.name != new_name.name;
10957
10958                if is_spark {
10959                    if is_rename {
10960                        // Spark: RENAME COLUMN old TO new
10961                        self.write_keyword("RENAME COLUMN");
10962                        self.write_space();
10963                        self.generate_identifier(old_name)?;
10964                        self.write_space();
10965                        self.write_keyword("TO");
10966                        self.write_space();
10967                        self.generate_identifier(new_name)?;
10968                    } else if comment.is_some() {
10969                        // Spark: ALTER COLUMN old COMMENT 'comment'
10970                        self.write_keyword("ALTER COLUMN");
10971                        self.write_space();
10972                        self.generate_identifier(old_name)?;
10973                        self.write_space();
10974                        self.write_keyword("COMMENT");
10975                        self.write_space();
10976                        self.write("'");
10977                        self.write(comment.as_ref().unwrap());
10978                        self.write("'");
10979                    } else if data_type.is_some() {
10980                        // Spark: ALTER COLUMN old TYPE data_type
10981                        self.write_keyword("ALTER COLUMN");
10982                        self.write_space();
10983                        self.generate_identifier(old_name)?;
10984                        self.write_space();
10985                        self.write_keyword("TYPE");
10986                        self.write_space();
10987                        self.generate_data_type(data_type.as_ref().unwrap())?;
10988                    } else {
10989                        // Fallback to CHANGE COLUMN
10990                        self.write_keyword("CHANGE COLUMN");
10991                        self.write_space();
10992                        self.generate_identifier(old_name)?;
10993                        self.write_space();
10994                        self.generate_identifier(new_name)?;
10995                    }
10996                } else {
10997                    // Hive/MySQL/default: CHANGE [COLUMN] old new [type] [COMMENT '...'] [CASCADE]
10998                    if data_type.is_some() {
10999                        self.write_keyword("CHANGE COLUMN");
11000                    } else {
11001                        self.write_keyword("CHANGE");
11002                    }
11003                    self.write_space();
11004                    self.generate_identifier(old_name)?;
11005                    self.write_space();
11006                    self.generate_identifier(new_name)?;
11007                    if let Some(ref dt) = data_type {
11008                        self.write_space();
11009                        self.generate_data_type(dt)?;
11010                    }
11011                    if let Some(ref c) = comment {
11012                        self.write_space();
11013                        self.write_keyword("COMMENT");
11014                        self.write_space();
11015                        self.write("'");
11016                        self.write(c);
11017                        self.write("'");
11018                    }
11019                    if *cascade {
11020                        self.write_space();
11021                        self.write_keyword("CASCADE");
11022                    }
11023                }
11024            }
11025            AlterTableAction::AddPartition {
11026                partition,
11027                if_not_exists,
11028                location,
11029            } => {
11030                self.write_keyword("ADD");
11031                self.write_space();
11032                if *if_not_exists {
11033                    self.write_keyword("IF NOT EXISTS");
11034                    self.write_space();
11035                }
11036                self.generate_expression(partition)?;
11037                if let Some(ref loc) = location {
11038                    self.write_space();
11039                    self.write_keyword("LOCATION");
11040                    self.write_space();
11041                    self.generate_expression(loc)?;
11042                }
11043            }
11044            AlterTableAction::AlterSortKey {
11045                this,
11046                expressions,
11047                compound,
11048            } => {
11049                // Redshift: ALTER [COMPOUND] SORTKEY AUTO|NONE|(col1, col2)
11050                self.write_keyword("ALTER");
11051                if *compound {
11052                    self.write_space();
11053                    self.write_keyword("COMPOUND");
11054                }
11055                self.write_space();
11056                self.write_keyword("SORTKEY");
11057                self.write_space();
11058                if let Some(style) = this {
11059                    self.write_keyword(style);
11060                } else if !expressions.is_empty() {
11061                    self.write("(");
11062                    for (i, expr) in expressions.iter().enumerate() {
11063                        if i > 0 {
11064                            self.write(", ");
11065                        }
11066                        self.generate_expression(expr)?;
11067                    }
11068                    self.write(")");
11069                }
11070            }
11071            AlterTableAction::AlterDistStyle { style, distkey } => {
11072                // Redshift: ALTER DISTSTYLE ALL|EVEN|AUTO|KEY [DISTKEY col]
11073                self.write_keyword("ALTER");
11074                self.write_space();
11075                self.write_keyword("DISTSTYLE");
11076                self.write_space();
11077                self.write_keyword(style);
11078                if let Some(col) = distkey {
11079                    self.write_space();
11080                    self.write_keyword("DISTKEY");
11081                    self.write_space();
11082                    self.generate_identifier(col)?;
11083                }
11084            }
11085            AlterTableAction::SetTableProperties { properties } => {
11086                // Redshift: SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')
11087                self.write_keyword("SET TABLE PROPERTIES");
11088                self.write(" (");
11089                for (i, (key, value)) in properties.iter().enumerate() {
11090                    if i > 0 {
11091                        self.write(", ");
11092                    }
11093                    self.generate_expression(key)?;
11094                    self.write(" = ");
11095                    self.generate_expression(value)?;
11096                }
11097                self.write(")");
11098            }
11099            AlterTableAction::SetLocation { location } => {
11100                // Redshift: SET LOCATION 's3://bucket/folder/'
11101                self.write_keyword("SET LOCATION");
11102                self.write_space();
11103                self.write("'");
11104                self.write(location);
11105                self.write("'");
11106            }
11107            AlterTableAction::SetFileFormat { format } => {
11108                // Redshift: SET FILE FORMAT AVRO
11109                self.write_keyword("SET FILE FORMAT");
11110                self.write_space();
11111                self.write_keyword(format);
11112            }
11113            AlterTableAction::ReplacePartition { partition, source } => {
11114                // ClickHouse: REPLACE PARTITION expr FROM source
11115                self.write_keyword("REPLACE PARTITION");
11116                self.write_space();
11117                self.generate_expression(partition)?;
11118                if let Some(src) = source {
11119                    self.write_space();
11120                    self.write_keyword("FROM");
11121                    self.write_space();
11122                    self.generate_expression(src)?;
11123                }
11124            }
11125            AlterTableAction::Raw { sql } => {
11126                self.write(sql);
11127            }
11128        }
11129        Ok(())
11130    }
11131
11132    fn generate_alter_column_action(&mut self, action: &AlterColumnAction) -> Result<()> {
11133        match action {
11134            AlterColumnAction::SetDataType {
11135                data_type,
11136                using,
11137                collate,
11138            } => {
11139                use crate::dialects::DialectType;
11140                // Dialect-specific type change syntax:
11141                // - TSQL/Fabric/Hive: no prefix (ALTER COLUMN col datatype)
11142                // - Redshift/Spark: TYPE (ALTER COLUMN col TYPE datatype)
11143                // - Default: SET DATA TYPE (ALTER COLUMN col SET DATA TYPE datatype)
11144                let is_no_prefix = matches!(
11145                    self.config.dialect,
11146                    Some(DialectType::TSQL) | Some(DialectType::Fabric) | Some(DialectType::Hive)
11147                );
11148                let is_type_only = matches!(
11149                    self.config.dialect,
11150                    Some(DialectType::Redshift)
11151                        | Some(DialectType::Spark)
11152                        | Some(DialectType::Databricks)
11153                );
11154                if is_type_only {
11155                    self.write_keyword("TYPE");
11156                    self.write_space();
11157                } else if !is_no_prefix {
11158                    self.write_keyword("SET DATA TYPE");
11159                    self.write_space();
11160                }
11161                self.generate_data_type(data_type)?;
11162                if let Some(ref collation) = collate {
11163                    self.write_space();
11164                    self.write_keyword("COLLATE");
11165                    self.write_space();
11166                    self.write(collation);
11167                }
11168                if let Some(ref using_expr) = using {
11169                    self.write_space();
11170                    self.write_keyword("USING");
11171                    self.write_space();
11172                    self.generate_expression(using_expr)?;
11173                }
11174            }
11175            AlterColumnAction::SetDefault(expr) => {
11176                self.write_keyword("SET DEFAULT");
11177                self.write_space();
11178                self.generate_expression(expr)?;
11179            }
11180            AlterColumnAction::DropDefault => {
11181                self.write_keyword("DROP DEFAULT");
11182            }
11183            AlterColumnAction::SetNotNull => {
11184                self.write_keyword("SET NOT NULL");
11185            }
11186            AlterColumnAction::DropNotNull => {
11187                self.write_keyword("DROP NOT NULL");
11188            }
11189            AlterColumnAction::Comment(comment) => {
11190                self.write_keyword("COMMENT");
11191                self.write_space();
11192                self.generate_string_literal(comment)?;
11193            }
11194            AlterColumnAction::SetVisible => {
11195                self.write_keyword("SET VISIBLE");
11196            }
11197            AlterColumnAction::SetInvisible => {
11198                self.write_keyword("SET INVISIBLE");
11199            }
11200        }
11201        Ok(())
11202    }
11203
11204    fn generate_create_index(&mut self, ci: &CreateIndex) -> Result<()> {
11205        self.write_keyword("CREATE");
11206
11207        if ci.unique {
11208            self.write_space();
11209            self.write_keyword("UNIQUE");
11210        }
11211
11212        // TSQL CLUSTERED/NONCLUSTERED modifier
11213        if let Some(ref clustered) = ci.clustered {
11214            self.write_space();
11215            self.write_keyword(clustered);
11216        }
11217
11218        self.write_space();
11219        self.write_keyword("INDEX");
11220
11221        // PostgreSQL CONCURRENTLY modifier
11222        if ci.concurrently {
11223            self.write_space();
11224            self.write_keyword("CONCURRENTLY");
11225        }
11226
11227        if ci.if_not_exists {
11228            self.write_space();
11229            self.write_keyword("IF NOT EXISTS");
11230        }
11231
11232        // Index name is optional in PostgreSQL when IF NOT EXISTS is specified
11233        if !ci.name.name.is_empty() {
11234            self.write_space();
11235            self.generate_identifier(&ci.name)?;
11236        }
11237        self.write_space();
11238        self.write_keyword("ON");
11239        // Hive uses ON TABLE
11240        if matches!(self.config.dialect, Some(DialectType::Hive)) {
11241            self.write_space();
11242            self.write_keyword("TABLE");
11243        }
11244        self.write_space();
11245        self.generate_table(&ci.table)?;
11246
11247        // Column list (optional for COLUMNSTORE indexes)
11248        // Standard SQL convention: ON t(a) without space before paren
11249        if !ci.columns.is_empty() || ci.using.is_some() {
11250            let space_before_paren = false;
11251
11252            if let Some(ref using) = ci.using {
11253                self.write_space();
11254                self.write_keyword("USING");
11255                self.write_space();
11256                self.write(using);
11257                if space_before_paren {
11258                    self.write(" (");
11259                } else {
11260                    self.write("(");
11261                }
11262            } else {
11263                if space_before_paren {
11264                    self.write(" (");
11265                } else {
11266                    self.write("(");
11267                }
11268            }
11269            for (i, col) in ci.columns.iter().enumerate() {
11270                if i > 0 {
11271                    self.write(", ");
11272                }
11273                self.generate_identifier(&col.column)?;
11274                if let Some(ref opclass) = col.opclass {
11275                    self.write_space();
11276                    self.write(opclass);
11277                }
11278                if col.desc {
11279                    self.write_space();
11280                    self.write_keyword("DESC");
11281                } else if col.asc {
11282                    self.write_space();
11283                    self.write_keyword("ASC");
11284                }
11285                if let Some(nulls_first) = col.nulls_first {
11286                    self.write_space();
11287                    self.write_keyword("NULLS");
11288                    self.write_space();
11289                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
11290                }
11291            }
11292            self.write(")");
11293        }
11294
11295        // PostgreSQL INCLUDE (col1, col2) clause
11296        if !ci.include_columns.is_empty() {
11297            self.write_space();
11298            self.write_keyword("INCLUDE");
11299            self.write(" (");
11300            for (i, col) in ci.include_columns.iter().enumerate() {
11301                if i > 0 {
11302                    self.write(", ");
11303                }
11304                self.generate_identifier(col)?;
11305            }
11306            self.write(")");
11307        }
11308
11309        // TSQL: WITH (option=value, ...) clause
11310        if !ci.with_options.is_empty() {
11311            self.write_space();
11312            self.write_keyword("WITH");
11313            self.write(" (");
11314            for (i, (key, value)) in ci.with_options.iter().enumerate() {
11315                if i > 0 {
11316                    self.write(", ");
11317                }
11318                self.write(key);
11319                self.write("=");
11320                self.write(value);
11321            }
11322            self.write(")");
11323        }
11324
11325        // PostgreSQL WHERE clause for partial indexes
11326        if let Some(ref where_clause) = ci.where_clause {
11327            self.write_space();
11328            self.write_keyword("WHERE");
11329            self.write_space();
11330            self.generate_expression(where_clause)?;
11331        }
11332
11333        // TSQL: ON filegroup or partition scheme clause
11334        if let Some(ref on_fg) = ci.on_filegroup {
11335            self.write_space();
11336            self.write_keyword("ON");
11337            self.write_space();
11338            self.write(on_fg);
11339        }
11340
11341        Ok(())
11342    }
11343
11344    fn generate_drop_index(&mut self, di: &DropIndex) -> Result<()> {
11345        self.write_keyword("DROP INDEX");
11346
11347        if di.concurrently {
11348            self.write_space();
11349            self.write_keyword("CONCURRENTLY");
11350        }
11351
11352        if di.if_exists {
11353            self.write_space();
11354            self.write_keyword("IF EXISTS");
11355        }
11356
11357        self.write_space();
11358        self.generate_table(&di.name)?;
11359
11360        if let Some(ref table) = di.table {
11361            self.write_space();
11362            self.write_keyword("ON");
11363            self.write_space();
11364            self.generate_table(table)?;
11365        }
11366
11367        Ok(())
11368    }
11369
11370    fn generate_create_view(&mut self, cv: &CreateView) -> Result<()> {
11371        self.write_keyword("CREATE");
11372
11373        // MySQL: ALGORITHM=...
11374        if let Some(ref algorithm) = cv.algorithm {
11375            self.write_space();
11376            self.write_keyword("ALGORITHM");
11377            self.write("=");
11378            self.write_keyword(algorithm);
11379        }
11380
11381        // MySQL: DEFINER=...
11382        if let Some(ref definer) = cv.definer {
11383            self.write_space();
11384            self.write_keyword("DEFINER");
11385            self.write("=");
11386            self.write(definer);
11387        }
11388
11389        // MySQL: SQL SECURITY DEFINER/INVOKER (before VIEW keyword, unless it appeared after view name)
11390        if cv.security_sql_style && !cv.security_after_name {
11391            if let Some(ref security) = cv.security {
11392                self.write_space();
11393                self.write_keyword("SQL SECURITY");
11394                self.write_space();
11395                match security {
11396                    FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11397                    FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11398                    FunctionSecurity::None => self.write_keyword("NONE"),
11399                }
11400            }
11401        }
11402
11403        if cv.or_alter {
11404            self.write_space();
11405            self.write_keyword("OR ALTER");
11406        } else if cv.or_replace {
11407            self.write_space();
11408            self.write_keyword("OR REPLACE");
11409        }
11410
11411        if cv.temporary {
11412            self.write_space();
11413            self.write_keyword("TEMPORARY");
11414        }
11415
11416        if cv.materialized {
11417            self.write_space();
11418            self.write_keyword("MATERIALIZED");
11419        }
11420
11421        // Snowflake: SECURE VIEW
11422        if cv.secure {
11423            self.write_space();
11424            self.write_keyword("SECURE");
11425        }
11426
11427        self.write_space();
11428        self.write_keyword("VIEW");
11429
11430        if cv.if_not_exists {
11431            self.write_space();
11432            self.write_keyword("IF NOT EXISTS");
11433        }
11434
11435        self.write_space();
11436        self.generate_table(&cv.name)?;
11437
11438        // ClickHouse: ON CLUSTER clause
11439        if let Some(ref on_cluster) = cv.on_cluster {
11440            self.write_space();
11441            self.generate_on_cluster(on_cluster)?;
11442        }
11443
11444        // ClickHouse: TO destination_table
11445        if let Some(ref to_table) = cv.to_table {
11446            self.write_space();
11447            self.write_keyword("TO");
11448            self.write_space();
11449            self.generate_table(to_table)?;
11450        }
11451
11452        // For regular VIEW: columns come before COPY GRANTS
11453        // For MATERIALIZED VIEW: COPY GRANTS comes before columns
11454        if !cv.materialized {
11455            // Regular VIEW: columns first
11456            if let Some(ref schema) = cv.schema {
11457                self.write(" (");
11458                for (i, expr) in schema.expressions.iter().enumerate() {
11459                    if i > 0 {
11460                        self.write(", ");
11461                    }
11462                    self.generate_expression(expr)?;
11463                }
11464                self.write(")");
11465            } else if !cv.columns.is_empty() {
11466                self.write(" (");
11467                for (i, col) in cv.columns.iter().enumerate() {
11468                    if i > 0 {
11469                        self.write(", ");
11470                    }
11471                    self.generate_identifier(&col.name)?;
11472                    // BigQuery: OPTIONS (key=value, ...) on view column
11473                    if !col.options.is_empty() {
11474                        self.write_space();
11475                        self.generate_options_clause(&col.options)?;
11476                    }
11477                    if let Some(ref comment) = col.comment {
11478                        self.write_space();
11479                        self.write_keyword("COMMENT");
11480                        self.write_space();
11481                        self.generate_string_literal(comment)?;
11482                    }
11483                }
11484                self.write(")");
11485            }
11486
11487            // Presto/Trino/StarRocks: SECURITY DEFINER/INVOKER/NONE (after columns)
11488            // Also handles SQL SECURITY after view name (security_after_name)
11489            if !cv.security_sql_style || cv.security_after_name {
11490                if let Some(ref security) = cv.security {
11491                    self.write_space();
11492                    if cv.security_sql_style {
11493                        self.write_keyword("SQL SECURITY");
11494                    } else {
11495                        self.write_keyword("SECURITY");
11496                    }
11497                    self.write_space();
11498                    match security {
11499                        FunctionSecurity::Definer => self.write_keyword("DEFINER"),
11500                        FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
11501                        FunctionSecurity::None => self.write_keyword("NONE"),
11502                    }
11503                }
11504            }
11505
11506            // Snowflake: COPY GRANTS
11507            if cv.copy_grants {
11508                self.write_space();
11509                self.write_keyword("COPY GRANTS");
11510            }
11511        } else {
11512            // MATERIALIZED VIEW: COPY GRANTS first
11513            if cv.copy_grants {
11514                self.write_space();
11515                self.write_keyword("COPY GRANTS");
11516            }
11517
11518            // Doris: If we have a schema (typed columns), generate that instead
11519            if let Some(ref schema) = cv.schema {
11520                self.write(" (");
11521                for (i, expr) in schema.expressions.iter().enumerate() {
11522                    if i > 0 {
11523                        self.write(", ");
11524                    }
11525                    self.generate_expression(expr)?;
11526                }
11527                self.write(")");
11528            } else if !cv.columns.is_empty() {
11529                // Then columns (simple column names without types)
11530                self.write(" (");
11531                for (i, col) in cv.columns.iter().enumerate() {
11532                    if i > 0 {
11533                        self.write(", ");
11534                    }
11535                    self.generate_identifier(&col.name)?;
11536                    // BigQuery: OPTIONS (key=value, ...) on view column
11537                    if !col.options.is_empty() {
11538                        self.write_space();
11539                        self.generate_options_clause(&col.options)?;
11540                    }
11541                    if let Some(ref comment) = col.comment {
11542                        self.write_space();
11543                        self.write_keyword("COMMENT");
11544                        self.write_space();
11545                        self.generate_string_literal(comment)?;
11546                    }
11547                }
11548                self.write(")");
11549            }
11550
11551            // Doris: KEY (columns) for materialized views
11552            if let Some(ref unique_key) = cv.unique_key {
11553                self.write_space();
11554                self.write_keyword("KEY");
11555                self.write(" (");
11556                for (i, expr) in unique_key.expressions.iter().enumerate() {
11557                    if i > 0 {
11558                        self.write(", ");
11559                    }
11560                    self.generate_expression(expr)?;
11561                }
11562                self.write(")");
11563            }
11564        }
11565
11566        if let Some(ref row_access_policy) = cv.row_access_policy {
11567            self.write_space();
11568            self.write_keyword("WITH");
11569            self.write_space();
11570            self.write(row_access_policy);
11571        }
11572
11573        // Snowflake: COMMENT = 'text'
11574        if let Some(ref comment) = cv.comment {
11575            self.write_space();
11576            self.write_keyword("COMMENT");
11577            self.write("=");
11578            self.generate_string_literal(comment)?;
11579        }
11580
11581        // Snowflake: TAG (name='value', ...)
11582        if !cv.tags.is_empty() {
11583            self.write_space();
11584            self.write_keyword("TAG");
11585            self.write(" (");
11586            for (i, (name, value)) in cv.tags.iter().enumerate() {
11587                if i > 0 {
11588                    self.write(", ");
11589                }
11590                self.write(name);
11591                self.write("='");
11592                self.write(value);
11593                self.write("'");
11594            }
11595            self.write(")");
11596        }
11597
11598        // BigQuery: OPTIONS (key=value, ...)
11599        if !cv.options.is_empty() {
11600            self.write_space();
11601            self.generate_options_clause(&cv.options)?;
11602        }
11603
11604        // Doris: BUILD IMMEDIATE/DEFERRED for materialized views
11605        if let Some(ref build) = cv.build {
11606            self.write_space();
11607            self.write_keyword("BUILD");
11608            self.write_space();
11609            self.write_keyword(build);
11610        }
11611
11612        // Doris: REFRESH clause for materialized views
11613        if let Some(ref refresh) = cv.refresh {
11614            self.write_space();
11615            self.generate_refresh_trigger_property(refresh)?;
11616        }
11617
11618        // Redshift: AUTO REFRESH YES|NO for materialized views
11619        if let Some(auto_refresh) = cv.auto_refresh {
11620            self.write_space();
11621            self.write_keyword("AUTO REFRESH");
11622            self.write_space();
11623            if auto_refresh {
11624                self.write_keyword("YES");
11625            } else {
11626                self.write_keyword("NO");
11627            }
11628        }
11629
11630        // ClickHouse: Table properties (ENGINE, ORDER BY, SAMPLE, SETTINGS, TTL, etc.)
11631        for prop in &cv.table_properties {
11632            self.write_space();
11633            self.generate_expression(prop)?;
11634        }
11635
11636        // ClickHouse: POPULATE / EMPTY before AS
11637        if let Some(ref population) = cv.clickhouse_population {
11638            self.write_space();
11639            self.write_keyword(population);
11640        }
11641
11642        // Only output AS clause if there's a real query (not just NULL placeholder)
11643        if !matches!(&cv.query, Expression::Null(_)) {
11644            self.write_space();
11645            self.write_keyword("AS");
11646            self.write_space();
11647
11648            // Teradata: LOCKING clause (between AS and query)
11649            if let Some(ref mode) = cv.locking_mode {
11650                self.write_keyword("LOCKING");
11651                self.write_space();
11652                self.write_keyword(mode);
11653                if let Some(ref access) = cv.locking_access {
11654                    self.write_space();
11655                    self.write_keyword("FOR");
11656                    self.write_space();
11657                    self.write_keyword(access);
11658                }
11659                self.write_space();
11660            }
11661
11662            if cv.query_parenthesized {
11663                self.write("(");
11664            }
11665            self.generate_expression(&cv.query)?;
11666            if cv.query_parenthesized {
11667                self.write(")");
11668            }
11669        }
11670
11671        // Redshift: WITH NO SCHEMA BINDING (after query)
11672        if cv.no_schema_binding {
11673            self.write_space();
11674            self.write_keyword("WITH NO SCHEMA BINDING");
11675        }
11676
11677        Ok(())
11678    }
11679
11680    fn generate_drop_view(&mut self, dv: &DropView) -> Result<()> {
11681        self.write_keyword("DROP");
11682
11683        if dv.materialized {
11684            self.write_space();
11685            self.write_keyword("MATERIALIZED");
11686        }
11687
11688        self.write_space();
11689        self.write_keyword("VIEW");
11690
11691        if dv.if_exists {
11692            self.write_space();
11693            self.write_keyword("IF EXISTS");
11694        }
11695
11696        self.write_space();
11697        self.generate_table(&dv.name)?;
11698
11699        Ok(())
11700    }
11701
11702    fn generate_truncate(&mut self, tr: &Truncate) -> Result<()> {
11703        match tr.target {
11704            TruncateTarget::Database => self.write_keyword("TRUNCATE DATABASE"),
11705            TruncateTarget::Table => self.write_keyword("TRUNCATE TABLE"),
11706        }
11707        if tr.if_exists {
11708            self.write_space();
11709            self.write_keyword("IF EXISTS");
11710        }
11711        self.write_space();
11712        self.generate_table(&tr.table)?;
11713
11714        // ClickHouse: ON CLUSTER clause
11715        if let Some(ref on_cluster) = tr.on_cluster {
11716            self.write_space();
11717            self.generate_on_cluster(on_cluster)?;
11718        }
11719
11720        // Check if first table has a * (multi-table with star)
11721        if !tr.extra_tables.is_empty() {
11722            // Check if the first entry matches the main table (star case)
11723            let skip_first = if let Some(first) = tr.extra_tables.first() {
11724                first.table.name == tr.table.name && first.star
11725            } else {
11726                false
11727            };
11728
11729            // PostgreSQL normalizes away the * suffix (it's the default behavior)
11730            let strip_star = matches!(
11731                self.config.dialect,
11732                Some(crate::dialects::DialectType::PostgreSQL)
11733                    | Some(crate::dialects::DialectType::Redshift)
11734            );
11735            if skip_first && !strip_star {
11736                self.write("*");
11737            }
11738
11739            // Generate additional tables
11740            for (i, entry) in tr.extra_tables.iter().enumerate() {
11741                if i == 0 && skip_first {
11742                    continue; // Already handled the star for first table
11743                }
11744                self.write(", ");
11745                self.generate_table(&entry.table)?;
11746                if entry.star && !strip_star {
11747                    self.write("*");
11748                }
11749            }
11750        }
11751
11752        // RESTART/CONTINUE IDENTITY
11753        if let Some(identity) = &tr.identity {
11754            self.write_space();
11755            match identity {
11756                TruncateIdentity::Restart => self.write_keyword("RESTART IDENTITY"),
11757                TruncateIdentity::Continue => self.write_keyword("CONTINUE IDENTITY"),
11758            }
11759        }
11760
11761        if tr.cascade {
11762            self.write_space();
11763            self.write_keyword("CASCADE");
11764        }
11765
11766        if tr.restrict {
11767            self.write_space();
11768            self.write_keyword("RESTRICT");
11769        }
11770
11771        // Output Hive PARTITION clause
11772        if let Some(ref partition) = tr.partition {
11773            self.write_space();
11774            self.generate_expression(partition)?;
11775        }
11776
11777        Ok(())
11778    }
11779
11780    fn generate_use(&mut self, u: &Use) -> Result<()> {
11781        // Teradata uses "DATABASE <name>" instead of "USE <name>"
11782        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
11783            self.write_keyword("DATABASE");
11784            self.write_space();
11785            self.generate_identifier(&u.this)?;
11786            return Ok(());
11787        }
11788
11789        self.write_keyword("USE");
11790
11791        if let Some(kind) = &u.kind {
11792            self.write_space();
11793            match kind {
11794                UseKind::Database => self.write_keyword("DATABASE"),
11795                UseKind::Schema => self.write_keyword("SCHEMA"),
11796                UseKind::Role => self.write_keyword("ROLE"),
11797                UseKind::Warehouse => self.write_keyword("WAREHOUSE"),
11798                UseKind::Catalog => self.write_keyword("CATALOG"),
11799                UseKind::SecondaryRoles => self.write_keyword("SECONDARY ROLES"),
11800            }
11801        }
11802
11803        self.write_space();
11804        // For SECONDARY ROLES, write the value as-is (ALL, NONE, or role names)
11805        // without quoting, since these are keywords not identifiers
11806        if matches!(&u.kind, Some(UseKind::SecondaryRoles)) {
11807            self.write(&u.this.name);
11808        } else {
11809            self.generate_identifier(&u.this)?;
11810        }
11811        Ok(())
11812    }
11813
11814    fn generate_cache(&mut self, c: &Cache) -> Result<()> {
11815        self.write_keyword("CACHE");
11816        if c.lazy {
11817            self.write_space();
11818            self.write_keyword("LAZY");
11819        }
11820        self.write_space();
11821        self.write_keyword("TABLE");
11822        self.write_space();
11823        self.generate_identifier(&c.table)?;
11824
11825        // OPTIONS clause
11826        if !c.options.is_empty() {
11827            self.write_space();
11828            self.write_keyword("OPTIONS");
11829            self.write("(");
11830            for (i, (key, value)) in c.options.iter().enumerate() {
11831                if i > 0 {
11832                    self.write(", ");
11833                }
11834                self.generate_expression(key)?;
11835                self.write(" = ");
11836                self.generate_expression(value)?;
11837            }
11838            self.write(")");
11839        }
11840
11841        // AS query
11842        if let Some(query) = &c.query {
11843            self.write_space();
11844            self.write_keyword("AS");
11845            self.write_space();
11846            self.generate_expression(query)?;
11847        }
11848
11849        Ok(())
11850    }
11851
11852    fn generate_uncache(&mut self, u: &Uncache) -> Result<()> {
11853        self.write_keyword("UNCACHE TABLE");
11854        if u.if_exists {
11855            self.write_space();
11856            self.write_keyword("IF EXISTS");
11857        }
11858        self.write_space();
11859        self.generate_identifier(&u.table)?;
11860        Ok(())
11861    }
11862
11863    fn generate_load_data(&mut self, l: &LoadData) -> Result<()> {
11864        self.write_keyword("LOAD DATA");
11865        if l.local {
11866            self.write_space();
11867            self.write_keyword("LOCAL");
11868        }
11869        self.write_space();
11870        self.write_keyword("INPATH");
11871        self.write_space();
11872        self.write("'");
11873        self.write(&l.inpath);
11874        self.write("'");
11875
11876        if l.overwrite {
11877            self.write_space();
11878            self.write_keyword("OVERWRITE");
11879        }
11880
11881        self.write_space();
11882        self.write_keyword("INTO TABLE");
11883        self.write_space();
11884        self.generate_expression(&l.table)?;
11885
11886        // PARTITION clause
11887        if !l.partition.is_empty() {
11888            self.write_space();
11889            self.write_keyword("PARTITION");
11890            self.write("(");
11891            for (i, (col, val)) in l.partition.iter().enumerate() {
11892                if i > 0 {
11893                    self.write(", ");
11894                }
11895                self.generate_identifier(col)?;
11896                self.write(" = ");
11897                self.generate_expression(val)?;
11898            }
11899            self.write(")");
11900        }
11901
11902        // INPUTFORMAT clause
11903        if let Some(fmt) = &l.input_format {
11904            self.write_space();
11905            self.write_keyword("INPUTFORMAT");
11906            self.write_space();
11907            self.write("'");
11908            self.write(fmt);
11909            self.write("'");
11910        }
11911
11912        // SERDE clause
11913        if let Some(serde) = &l.serde {
11914            self.write_space();
11915            self.write_keyword("SERDE");
11916            self.write_space();
11917            self.write("'");
11918            self.write(serde);
11919            self.write("'");
11920        }
11921
11922        Ok(())
11923    }
11924
11925    fn generate_pragma(&mut self, p: &Pragma) -> Result<()> {
11926        self.write_keyword("PRAGMA");
11927        self.write_space();
11928
11929        // Schema prefix if present
11930        if let Some(schema) = &p.schema {
11931            self.generate_identifier(schema)?;
11932            self.write(".");
11933        }
11934
11935        // Pragma name
11936        self.generate_identifier(&p.name)?;
11937
11938        // Value assignment or function call
11939        if p.use_assignment_syntax {
11940            self.write(" = ");
11941            if let Some(value) = &p.value {
11942                self.generate_expression(value)?;
11943            } else if let Some(arg) = p.args.first() {
11944                self.generate_expression(arg)?;
11945            }
11946        } else if !p.args.is_empty() {
11947            self.write("(");
11948            for (i, arg) in p.args.iter().enumerate() {
11949                if i > 0 {
11950                    self.write(", ");
11951                }
11952                self.generate_expression(arg)?;
11953            }
11954            self.write(")");
11955        }
11956
11957        Ok(())
11958    }
11959
11960    fn generate_grant(&mut self, g: &Grant) -> Result<()> {
11961        self.write_keyword("GRANT");
11962        self.write_space();
11963
11964        // Privileges (with optional column lists)
11965        for (i, privilege) in g.privileges.iter().enumerate() {
11966            if i > 0 {
11967                self.write(", ");
11968            }
11969            self.write_keyword(&privilege.name);
11970            // Output column list if present: SELECT(col1, col2)
11971            if !privilege.columns.is_empty() {
11972                self.write("(");
11973                for (j, col) in privilege.columns.iter().enumerate() {
11974                    if j > 0 {
11975                        self.write(", ");
11976                    }
11977                    self.write(col);
11978                }
11979                self.write(")");
11980            }
11981        }
11982
11983        self.write_space();
11984        self.write_keyword("ON");
11985        self.write_space();
11986
11987        // Object kind (TABLE, SCHEMA, etc.)
11988        if let Some(kind) = &g.kind {
11989            self.write_keyword(kind);
11990            self.write_space();
11991        }
11992
11993        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
11994        {
11995            use crate::dialects::DialectType;
11996            let should_upper = matches!(
11997                self.config.dialect,
11998                Some(DialectType::PostgreSQL)
11999                    | Some(DialectType::CockroachDB)
12000                    | Some(DialectType::Materialize)
12001                    | Some(DialectType::RisingWave)
12002            ) && (g.kind.as_deref() == Some("FUNCTION")
12003                || g.kind.as_deref() == Some("PROCEDURE"));
12004            if should_upper {
12005                use crate::expressions::Identifier;
12006                let upper_id = Identifier {
12007                    name: g.securable.name.to_ascii_uppercase(),
12008                    quoted: g.securable.quoted,
12009                    ..g.securable.clone()
12010                };
12011                self.generate_identifier(&upper_id)?;
12012            } else {
12013                self.generate_identifier(&g.securable)?;
12014            }
12015        }
12016
12017        // Function parameter types (if present)
12018        if !g.function_params.is_empty() {
12019            self.write("(");
12020            for (i, param) in g.function_params.iter().enumerate() {
12021                if i > 0 {
12022                    self.write(", ");
12023                }
12024                self.write(param);
12025            }
12026            self.write(")");
12027        }
12028
12029        self.write_space();
12030        self.write_keyword("TO");
12031        self.write_space();
12032
12033        // Principals
12034        for (i, principal) in g.principals.iter().enumerate() {
12035            if i > 0 {
12036                self.write(", ");
12037            }
12038            if principal.is_role {
12039                self.write_keyword("ROLE");
12040                self.write_space();
12041            } else if principal.is_group {
12042                self.write_keyword("GROUP");
12043                self.write_space();
12044            } else if principal.is_share {
12045                self.write_keyword("SHARE");
12046                self.write_space();
12047            }
12048            self.generate_identifier(&principal.name)?;
12049        }
12050
12051        // WITH GRANT OPTION
12052        if g.grant_option {
12053            self.write_space();
12054            self.write_keyword("WITH GRANT OPTION");
12055        }
12056
12057        // TSQL: AS principal
12058        if let Some(ref principal) = g.as_principal {
12059            self.write_space();
12060            self.write_keyword("AS");
12061            self.write_space();
12062            self.generate_identifier(principal)?;
12063        }
12064
12065        Ok(())
12066    }
12067
12068    fn generate_revoke(&mut self, r: &Revoke) -> Result<()> {
12069        self.write_keyword("REVOKE");
12070        self.write_space();
12071
12072        // GRANT OPTION FOR
12073        if r.grant_option {
12074            self.write_keyword("GRANT OPTION FOR");
12075            self.write_space();
12076        }
12077
12078        // Privileges (with optional column lists)
12079        for (i, privilege) in r.privileges.iter().enumerate() {
12080            if i > 0 {
12081                self.write(", ");
12082            }
12083            self.write_keyword(&privilege.name);
12084            // Output column list if present: SELECT(col1, col2)
12085            if !privilege.columns.is_empty() {
12086                self.write("(");
12087                for (j, col) in privilege.columns.iter().enumerate() {
12088                    if j > 0 {
12089                        self.write(", ");
12090                    }
12091                    self.write(col);
12092                }
12093                self.write(")");
12094            }
12095        }
12096
12097        self.write_space();
12098        self.write_keyword("ON");
12099        self.write_space();
12100
12101        // Object kind
12102        if let Some(kind) = &r.kind {
12103            self.write_keyword(kind);
12104            self.write_space();
12105        }
12106
12107        // Securable - normalize function/procedure names to uppercase for PostgreSQL family
12108        {
12109            use crate::dialects::DialectType;
12110            let should_upper = matches!(
12111                self.config.dialect,
12112                Some(DialectType::PostgreSQL)
12113                    | Some(DialectType::CockroachDB)
12114                    | Some(DialectType::Materialize)
12115                    | Some(DialectType::RisingWave)
12116            ) && (r.kind.as_deref() == Some("FUNCTION")
12117                || r.kind.as_deref() == Some("PROCEDURE"));
12118            if should_upper {
12119                use crate::expressions::Identifier;
12120                let upper_id = Identifier {
12121                    name: r.securable.name.to_ascii_uppercase(),
12122                    quoted: r.securable.quoted,
12123                    ..r.securable.clone()
12124                };
12125                self.generate_identifier(&upper_id)?;
12126            } else {
12127                self.generate_identifier(&r.securable)?;
12128            }
12129        }
12130
12131        // Function parameter types (if present)
12132        if !r.function_params.is_empty() {
12133            self.write("(");
12134            for (i, param) in r.function_params.iter().enumerate() {
12135                if i > 0 {
12136                    self.write(", ");
12137                }
12138                self.write(param);
12139            }
12140            self.write(")");
12141        }
12142
12143        self.write_space();
12144        self.write_keyword("FROM");
12145        self.write_space();
12146
12147        // Principals
12148        for (i, principal) in r.principals.iter().enumerate() {
12149            if i > 0 {
12150                self.write(", ");
12151            }
12152            if principal.is_role {
12153                self.write_keyword("ROLE");
12154                self.write_space();
12155            } else if principal.is_group {
12156                self.write_keyword("GROUP");
12157                self.write_space();
12158            } else if principal.is_share {
12159                self.write_keyword("SHARE");
12160                self.write_space();
12161            }
12162            self.generate_identifier(&principal.name)?;
12163        }
12164
12165        // CASCADE or RESTRICT
12166        if r.cascade {
12167            self.write_space();
12168            self.write_keyword("CASCADE");
12169        } else if r.restrict {
12170            self.write_space();
12171            self.write_keyword("RESTRICT");
12172        }
12173
12174        Ok(())
12175    }
12176
12177    fn generate_comment(&mut self, c: &Comment) -> Result<()> {
12178        self.write_keyword("COMMENT");
12179
12180        // IF EXISTS
12181        if c.exists {
12182            self.write_space();
12183            self.write_keyword("IF EXISTS");
12184        }
12185
12186        self.write_space();
12187        self.write_keyword("ON");
12188
12189        // MATERIALIZED
12190        if c.materialized {
12191            self.write_space();
12192            self.write_keyword("MATERIALIZED");
12193        }
12194
12195        self.write_space();
12196        self.write_keyword(&c.kind);
12197        self.write_space();
12198
12199        // Object name
12200        self.generate_expression(&c.this)?;
12201
12202        self.write_space();
12203        self.write_keyword("IS");
12204        self.write_space();
12205
12206        // Comment expression
12207        self.generate_expression(&c.expression)?;
12208
12209        Ok(())
12210    }
12211
12212    fn generate_set_statement(&mut self, s: &SetStatement) -> Result<()> {
12213        self.write_keyword("SET");
12214
12215        for (i, item) in s.items.iter().enumerate() {
12216            if i > 0 {
12217                self.write(",");
12218            }
12219            self.write_space();
12220
12221            // Kind modifier (GLOBAL, LOCAL, SESSION, PERSIST, PERSIST_ONLY, VARIABLE)
12222            let has_variable_kind = item.kind.as_deref() == Some("VARIABLE");
12223            if let Some(ref kind) = item.kind {
12224                // For VARIABLE kind, only output the keyword for dialects that require it
12225                // (Spark, Databricks, DuckDB) - matching Python sqlglot's
12226                // SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD flag
12227                if has_variable_kind {
12228                    if matches!(
12229                        self.config.dialect,
12230                        Some(DialectType::Spark | DialectType::Databricks | DialectType::DuckDB)
12231                    ) {
12232                        self.write_keyword("VARIABLE");
12233                        self.write_space();
12234                    }
12235                } else {
12236                    self.write_keyword(kind);
12237                    self.write_space();
12238                }
12239            }
12240
12241            // Check for special SET forms by name
12242            let name_str = match &item.name {
12243                Expression::Identifier(id) => Some(id.name.as_str()),
12244                _ => None,
12245            };
12246
12247            let is_transaction = name_str == Some("TRANSACTION");
12248            let is_character_set = name_str == Some("CHARACTER SET");
12249            let is_names = name_str == Some("NAMES");
12250            let is_collate = name_str == Some("COLLATE");
12251            let is_identity_insert = name_str == Some("IDENTITY_INSERT");
12252            let is_value_only =
12253                matches!(&item.value, Expression::Identifier(id) if id.name.is_empty());
12254
12255            if is_transaction {
12256                // Output: SET [GLOBAL|SESSION] TRANSACTION <characteristics>
12257                self.write_keyword("TRANSACTION");
12258                if let Expression::Identifier(id) = &item.value {
12259                    if !id.name.is_empty() {
12260                        self.write_space();
12261                        self.write(&id.name);
12262                    }
12263                }
12264            } else if is_character_set {
12265                // Output: SET CHARACTER SET <charset>
12266                self.write_keyword("CHARACTER SET");
12267                self.write_space();
12268                self.generate_set_value(&item.value)?;
12269            } else if is_names {
12270                // Output: SET NAMES <charset>
12271                self.write_keyword("NAMES");
12272                self.write_space();
12273                self.generate_set_value(&item.value)?;
12274            } else if is_collate {
12275                // Output: COLLATE <collation> (part of SET NAMES ... COLLATE ...)
12276                self.write_keyword("COLLATE");
12277                self.write_space();
12278                self.generate_set_value(&item.value)?;
12279            } else if is_identity_insert {
12280                // T-SQL: SET IDENTITY_INSERT <table> ON|OFF
12281                self.write_keyword("IDENTITY_INSERT");
12282                self.write_space();
12283                self.generate_identity_insert_value(&item.value)?;
12284            } else if has_variable_kind {
12285                // Output: SET [VARIABLE] <name> = <value>
12286                // VARIABLE keyword already written above if dialect requires it
12287                if let Some(ns) = name_str {
12288                    self.write(ns);
12289                } else {
12290                    self.generate_expression(&item.name)?;
12291                }
12292                self.write(" = ");
12293                self.generate_set_value(&item.value)?;
12294            } else if is_value_only {
12295                // SET <name> ON/OFF without = (TSQL: SET XACT_ABORT ON)
12296                self.generate_expression(&item.name)?;
12297            } else if item.no_equals && matches!(self.config.dialect, Some(DialectType::TSQL)) {
12298                // SET key value without = (TSQL style)
12299                self.generate_expression(&item.name)?;
12300                self.write_space();
12301                self.generate_set_value(&item.value)?;
12302            } else {
12303                // Standard: variable = value
12304                // SET item names should not be quoted (they are config parameter names, not column refs)
12305                match &item.name {
12306                    Expression::Identifier(id) => {
12307                        self.write(&id.name);
12308                    }
12309                    _ => {
12310                        self.generate_expression(&item.name)?;
12311                    }
12312                }
12313                self.write(" = ");
12314                self.generate_set_value(&item.value)?;
12315            }
12316        }
12317
12318        Ok(())
12319    }
12320
12321    fn generate_identity_insert_value(&mut self, value: &Expression) -> Result<()> {
12322        if let Expression::Tuple(tuple) = value {
12323            if tuple.expressions.len() == 2 {
12324                self.generate_expression(&tuple.expressions[0])?;
12325                self.write_space();
12326                self.generate_set_value(&tuple.expressions[1])?;
12327                return Ok(());
12328            }
12329        }
12330
12331        self.generate_set_value(value)
12332    }
12333
12334    /// Generate a SET statement value, writing keyword values (DEFAULT, ON, OFF)
12335    /// directly to avoid reserved keyword quoting.
12336    fn generate_set_value(&mut self, value: &Expression) -> Result<()> {
12337        if let Expression::Identifier(id) = value {
12338            match id.name.as_str() {
12339                "DEFAULT" | "ON" | "OFF" => {
12340                    self.write_keyword(&id.name);
12341                    return Ok(());
12342                }
12343                _ => {}
12344            }
12345        }
12346        self.generate_expression(value)
12347    }
12348
12349    // ==================== Phase 4: Additional DDL Generation ====================
12350
12351    fn generate_alter_view(&mut self, av: &AlterView) -> Result<()> {
12352        self.write_keyword("ALTER");
12353        // MySQL modifiers before VIEW
12354        if let Some(ref algorithm) = av.algorithm {
12355            self.write_space();
12356            self.write_keyword("ALGORITHM");
12357            self.write(" = ");
12358            self.write_keyword(algorithm);
12359        }
12360        if let Some(ref definer) = av.definer {
12361            self.write_space();
12362            self.write_keyword("DEFINER");
12363            self.write(" = ");
12364            self.write(definer);
12365        }
12366        if let Some(ref sql_security) = av.sql_security {
12367            self.write_space();
12368            self.write_keyword("SQL SECURITY");
12369            self.write(" = ");
12370            self.write_keyword(sql_security);
12371        }
12372        self.write_space();
12373        self.write_keyword("VIEW");
12374        self.write_space();
12375        self.generate_table(&av.name)?;
12376
12377        // Hive: Column aliases with optional COMMENT
12378        if !av.columns.is_empty() {
12379            self.write(" (");
12380            for (i, col) in av.columns.iter().enumerate() {
12381                if i > 0 {
12382                    self.write(", ");
12383                }
12384                self.generate_identifier(&col.name)?;
12385                if let Some(ref comment) = col.comment {
12386                    self.write_space();
12387                    self.write_keyword("COMMENT");
12388                    self.write(" ");
12389                    self.generate_string_literal(comment)?;
12390                }
12391            }
12392            self.write(")");
12393        }
12394
12395        // TSQL: WITH option before actions
12396        if let Some(ref opt) = av.with_option {
12397            self.write_space();
12398            self.write_keyword("WITH");
12399            self.write_space();
12400            self.write_keyword(opt);
12401        }
12402
12403        for action in &av.actions {
12404            self.write_space();
12405            match action {
12406                AlterViewAction::Rename(new_name) => {
12407                    self.write_keyword("RENAME TO");
12408                    self.write_space();
12409                    self.generate_table(new_name)?;
12410                }
12411                AlterViewAction::OwnerTo(owner) => {
12412                    self.write_keyword("OWNER TO");
12413                    self.write_space();
12414                    self.generate_identifier(owner)?;
12415                }
12416                AlterViewAction::SetSchema(schema) => {
12417                    self.write_keyword("SET SCHEMA");
12418                    self.write_space();
12419                    self.generate_identifier(schema)?;
12420                }
12421                AlterViewAction::SetAuthorization(auth) => {
12422                    self.write_keyword("SET AUTHORIZATION");
12423                    self.write_space();
12424                    self.write(auth);
12425                }
12426                AlterViewAction::AlterColumn { name, action } => {
12427                    self.write_keyword("ALTER COLUMN");
12428                    self.write_space();
12429                    self.generate_identifier(name)?;
12430                    self.write_space();
12431                    self.generate_alter_column_action(action)?;
12432                }
12433                AlterViewAction::AsSelect(query) => {
12434                    self.write_keyword("AS");
12435                    self.write_space();
12436                    self.generate_expression(query)?;
12437                }
12438                AlterViewAction::SetTblproperties(props) => {
12439                    self.write_keyword("SET TBLPROPERTIES");
12440                    self.write(" (");
12441                    for (i, (key, value)) in props.iter().enumerate() {
12442                        if i > 0 {
12443                            self.write(", ");
12444                        }
12445                        self.generate_string_literal(key)?;
12446                        self.write("=");
12447                        self.generate_string_literal(value)?;
12448                    }
12449                    self.write(")");
12450                }
12451                AlterViewAction::UnsetTblproperties(keys) => {
12452                    self.write_keyword("UNSET TBLPROPERTIES");
12453                    self.write(" (");
12454                    for (i, key) in keys.iter().enumerate() {
12455                        if i > 0 {
12456                            self.write(", ");
12457                        }
12458                        self.generate_string_literal(key)?;
12459                    }
12460                    self.write(")");
12461                }
12462            }
12463        }
12464
12465        Ok(())
12466    }
12467
12468    fn generate_alter_index(&mut self, ai: &AlterIndex) -> Result<()> {
12469        self.write_keyword("ALTER INDEX");
12470        self.write_space();
12471        self.generate_identifier(&ai.name)?;
12472
12473        if let Some(table) = &ai.table {
12474            self.write_space();
12475            self.write_keyword("ON");
12476            self.write_space();
12477            self.generate_table(table)?;
12478        }
12479
12480        for action in &ai.actions {
12481            self.write_space();
12482            match action {
12483                AlterIndexAction::Rename(new_name) => {
12484                    self.write_keyword("RENAME TO");
12485                    self.write_space();
12486                    self.generate_identifier(new_name)?;
12487                }
12488                AlterIndexAction::SetTablespace(tablespace) => {
12489                    self.write_keyword("SET TABLESPACE");
12490                    self.write_space();
12491                    self.generate_identifier(tablespace)?;
12492                }
12493                AlterIndexAction::Visible(visible) => {
12494                    if *visible {
12495                        self.write_keyword("VISIBLE");
12496                    } else {
12497                        self.write_keyword("INVISIBLE");
12498                    }
12499                }
12500            }
12501        }
12502
12503        Ok(())
12504    }
12505
12506    fn generate_create_schema(&mut self, cs: &CreateSchema) -> Result<()> {
12507        // Output leading comments
12508        for comment in &cs.leading_comments {
12509            self.write_formatted_comment(comment);
12510            self.write_space();
12511        }
12512
12513        // Athena: CREATE SCHEMA uses Hive engine (backticks)
12514        let saved_athena_hive_context = self.athena_hive_context;
12515        if matches!(
12516            self.config.dialect,
12517            Some(crate::dialects::DialectType::Athena)
12518        ) {
12519            self.athena_hive_context = true;
12520        }
12521
12522        self.write_keyword("CREATE SCHEMA");
12523
12524        if cs.if_not_exists {
12525            self.write_space();
12526            self.write_keyword("IF NOT EXISTS");
12527        }
12528
12529        self.write_space();
12530        for (i, part) in cs.name.iter().enumerate() {
12531            if i > 0 {
12532                self.write(".");
12533            }
12534            self.generate_identifier(part)?;
12535        }
12536
12537        if let Some(ref clone_parts) = cs.clone_from {
12538            self.write_keyword(" CLONE ");
12539            for (i, part) in clone_parts.iter().enumerate() {
12540                if i > 0 {
12541                    self.write(".");
12542                }
12543                self.generate_identifier(part)?;
12544            }
12545        }
12546
12547        if let Some(ref at_clause) = cs.at_clause {
12548            self.write_space();
12549            self.generate_expression(at_clause)?;
12550        }
12551
12552        if let Some(auth) = &cs.authorization {
12553            self.write_space();
12554            self.write_keyword("AUTHORIZATION");
12555            self.write_space();
12556            self.generate_identifier(auth)?;
12557        }
12558
12559        // Generate schema properties (e.g., DEFAULT COLLATE or WITH (props))
12560        // Separate WITH properties from other properties
12561        let with_properties: Vec<_> = cs
12562            .properties
12563            .iter()
12564            .filter(|p| matches!(p, Expression::Property(_)))
12565            .collect();
12566        let other_properties: Vec<_> = cs
12567            .properties
12568            .iter()
12569            .filter(|p| !matches!(p, Expression::Property(_)))
12570            .collect();
12571
12572        // Generate WITH (props) if we have Property expressions
12573        if !with_properties.is_empty() {
12574            self.write_space();
12575            self.write_keyword("WITH");
12576            self.write(" (");
12577            for (i, prop) in with_properties.iter().enumerate() {
12578                if i > 0 {
12579                    self.write(", ");
12580                }
12581                self.generate_expression(prop)?;
12582            }
12583            self.write(")");
12584        }
12585
12586        // Generate other properties (like DEFAULT COLLATE)
12587        for prop in other_properties {
12588            self.write_space();
12589            self.generate_expression(prop)?;
12590        }
12591
12592        // Restore Athena Hive context
12593        self.athena_hive_context = saved_athena_hive_context;
12594
12595        Ok(())
12596    }
12597
12598    fn generate_drop_schema(&mut self, ds: &DropSchema) -> Result<()> {
12599        self.write_keyword("DROP SCHEMA");
12600
12601        if ds.if_exists {
12602            self.write_space();
12603            self.write_keyword("IF EXISTS");
12604        }
12605
12606        self.write_space();
12607        self.generate_identifier(&ds.name)?;
12608
12609        if ds.cascade {
12610            self.write_space();
12611            self.write_keyword("CASCADE");
12612        }
12613
12614        Ok(())
12615    }
12616
12617    fn generate_drop_namespace(&mut self, dn: &DropNamespace) -> Result<()> {
12618        self.write_keyword("DROP NAMESPACE");
12619
12620        if dn.if_exists {
12621            self.write_space();
12622            self.write_keyword("IF EXISTS");
12623        }
12624
12625        self.write_space();
12626        self.generate_identifier(&dn.name)?;
12627
12628        if dn.cascade {
12629            self.write_space();
12630            self.write_keyword("CASCADE");
12631        }
12632
12633        Ok(())
12634    }
12635
12636    fn generate_create_database(&mut self, cd: &CreateDatabase) -> Result<()> {
12637        self.write_keyword("CREATE DATABASE");
12638
12639        if cd.if_not_exists {
12640            self.write_space();
12641            self.write_keyword("IF NOT EXISTS");
12642        }
12643
12644        self.write_space();
12645        self.generate_identifier(&cd.name)?;
12646
12647        if let Some(ref clone_src) = cd.clone_from {
12648            self.write_keyword(" CLONE ");
12649            self.generate_identifier(clone_src)?;
12650        }
12651
12652        // AT/BEFORE clause for time travel (Snowflake)
12653        if let Some(ref at_clause) = cd.at_clause {
12654            self.write_space();
12655            self.generate_expression(at_clause)?;
12656        }
12657
12658        for option in &cd.options {
12659            self.write_space();
12660            match option {
12661                DatabaseOption::CharacterSet(charset) => {
12662                    self.write_keyword("CHARACTER SET");
12663                    self.write(" = ");
12664                    self.write(&format!("'{}'", charset));
12665                }
12666                DatabaseOption::Collate(collate) => {
12667                    self.write_keyword("COLLATE");
12668                    self.write(" = ");
12669                    self.write(&format!("'{}'", collate));
12670                }
12671                DatabaseOption::Owner(owner) => {
12672                    self.write_keyword("OWNER");
12673                    self.write(" = ");
12674                    self.generate_identifier(owner)?;
12675                }
12676                DatabaseOption::Template(template) => {
12677                    self.write_keyword("TEMPLATE");
12678                    self.write(" = ");
12679                    self.generate_identifier(template)?;
12680                }
12681                DatabaseOption::Encoding(encoding) => {
12682                    self.write_keyword("ENCODING");
12683                    self.write(" = ");
12684                    self.write(&format!("'{}'", encoding));
12685                }
12686                DatabaseOption::Location(location) => {
12687                    self.write_keyword("LOCATION");
12688                    self.write(" = ");
12689                    self.write(&format!("'{}'", location));
12690                }
12691            }
12692        }
12693
12694        Ok(())
12695    }
12696
12697    fn generate_drop_database(&mut self, dd: &DropDatabase) -> Result<()> {
12698        self.write_keyword("DROP DATABASE");
12699
12700        if dd.if_exists {
12701            self.write_space();
12702            self.write_keyword("IF EXISTS");
12703        }
12704
12705        self.write_space();
12706        self.generate_identifier(&dd.name)?;
12707
12708        if dd.sync {
12709            self.write_space();
12710            self.write_keyword("SYNC");
12711        }
12712
12713        Ok(())
12714    }
12715
12716    fn generate_create_function(&mut self, cf: &CreateFunction) -> Result<()> {
12717        self.write_keyword("CREATE");
12718
12719        if cf.or_alter {
12720            self.write_space();
12721            self.write_keyword("OR ALTER");
12722        } else if cf.or_replace {
12723            self.write_space();
12724            self.write_keyword("OR REPLACE");
12725        }
12726
12727        if cf.temporary {
12728            self.write_space();
12729            self.write_keyword("TEMPORARY");
12730        }
12731
12732        self.write_space();
12733        if cf.is_table_function {
12734            self.write_keyword("TABLE FUNCTION");
12735        } else {
12736            self.write_keyword("FUNCTION");
12737        }
12738
12739        if cf.if_not_exists {
12740            self.write_space();
12741            self.write_keyword("IF NOT EXISTS");
12742        }
12743
12744        self.write_space();
12745        self.generate_table(&cf.name)?;
12746        if cf.has_parens {
12747            let func_multiline = self.config.pretty
12748                && matches!(
12749                    self.config.dialect,
12750                    Some(crate::dialects::DialectType::TSQL)
12751                        | Some(crate::dialects::DialectType::Fabric)
12752                )
12753                && !cf.parameters.is_empty();
12754            if func_multiline {
12755                self.write("(\n");
12756                self.indent_level += 2;
12757                self.write_indent();
12758                self.generate_function_parameters(&cf.parameters)?;
12759                self.write("\n");
12760                self.indent_level -= 2;
12761                self.write(")");
12762            } else {
12763                self.write("(");
12764                self.generate_function_parameters(&cf.parameters)?;
12765                self.write(")");
12766            }
12767        }
12768
12769        // Output RETURNS clause (always comes first after parameters)
12770        // BigQuery and TSQL use multiline formatting for CREATE FUNCTION structure
12771        let use_multiline = self.config.pretty
12772            && matches!(
12773                self.config.dialect,
12774                Some(crate::dialects::DialectType::BigQuery)
12775                    | Some(crate::dialects::DialectType::TSQL)
12776                    | Some(crate::dialects::DialectType::Fabric)
12777            );
12778
12779        if cf.language_first {
12780            // LANGUAGE first, then SQL data access, then RETURNS
12781            if let Some(lang) = &cf.language {
12782                if use_multiline {
12783                    self.write_newline();
12784                } else {
12785                    self.write_space();
12786                }
12787                self.write_keyword("LANGUAGE");
12788                self.write_space();
12789                self.write(lang);
12790            }
12791
12792            // SQL data access comes after LANGUAGE in this case
12793            if let Some(sql_data) = &cf.sql_data_access {
12794                self.write_space();
12795                match sql_data {
12796                    SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
12797                    SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
12798                    SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
12799                    SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
12800                }
12801            }
12802
12803            if let Some(ref rtb) = cf.returns_table_body {
12804                if use_multiline {
12805                    self.write_newline();
12806                } else {
12807                    self.write_space();
12808                }
12809                self.write_keyword("RETURNS");
12810                self.write_space();
12811                self.write(rtb);
12812            } else if let Some(return_type) = &cf.return_type {
12813                if use_multiline {
12814                    self.write_newline();
12815                } else {
12816                    self.write_space();
12817                }
12818                self.write_keyword("RETURNS");
12819                self.write_space();
12820                self.generate_data_type(return_type)?;
12821            }
12822        } else {
12823            // RETURNS first (default)
12824            // DuckDB macros: skip RETURNS output (empty marker in returns_table_body means TABLE return)
12825            let is_duckdb = matches!(
12826                self.config.dialect,
12827                Some(crate::dialects::DialectType::DuckDB)
12828            );
12829            if let Some(ref rtb) = cf.returns_table_body {
12830                if !(is_duckdb && rtb.is_empty()) {
12831                    if use_multiline {
12832                        self.write_newline();
12833                    } else {
12834                        self.write_space();
12835                    }
12836                    self.write_keyword("RETURNS");
12837                    self.write_space();
12838                    self.write(rtb);
12839                }
12840            } else if let Some(return_type) = &cf.return_type {
12841                // DuckDB: skip all RETURNS (DuckDB macros don't use RETURNS clause)
12842                if !is_duckdb {
12843                    let is_table_return = matches!(return_type, crate::expressions::DataType::Custom { ref name } if name.eq_ignore_ascii_case("TABLE"));
12844                    if use_multiline {
12845                        self.write_newline();
12846                    } else {
12847                        self.write_space();
12848                    }
12849                    self.write_keyword("RETURNS");
12850                    self.write_space();
12851                    if is_table_return {
12852                        self.write_keyword("TABLE");
12853                    } else {
12854                        self.generate_data_type(return_type)?;
12855                    }
12856                }
12857            }
12858        }
12859
12860        // If we have property_order, use it to output properties in original order
12861        if !cf.property_order.is_empty() {
12862            // For BigQuery, OPTIONS must come before AS - reorder if needed
12863            let is_bigquery = matches!(
12864                self.config.dialect,
12865                Some(crate::dialects::DialectType::BigQuery)
12866            );
12867            let property_order = if is_bigquery {
12868                // Move Options before As if both are present
12869                let mut reordered = Vec::new();
12870                let mut has_as = false;
12871                let mut has_options = false;
12872                for prop in &cf.property_order {
12873                    match prop {
12874                        FunctionPropertyKind::As => has_as = true,
12875                        FunctionPropertyKind::Options => has_options = true,
12876                        _ => {}
12877                    }
12878                }
12879                if has_as && has_options {
12880                    // Output all props except As and Options, then Options, then As
12881                    for prop in &cf.property_order {
12882                        if *prop != FunctionPropertyKind::As
12883                            && *prop != FunctionPropertyKind::Options
12884                        {
12885                            reordered.push(*prop);
12886                        }
12887                    }
12888                    reordered.push(FunctionPropertyKind::Options);
12889                    reordered.push(FunctionPropertyKind::As);
12890                    reordered
12891                } else {
12892                    cf.property_order.clone()
12893                }
12894            } else {
12895                cf.property_order.clone()
12896            };
12897
12898            for prop in &property_order {
12899                match prop {
12900                    FunctionPropertyKind::Set => {
12901                        self.generate_function_set_options(cf)?;
12902                    }
12903                    FunctionPropertyKind::As => {
12904                        self.generate_function_body(cf)?;
12905                    }
12906                    FunctionPropertyKind::Using => {
12907                        self.generate_function_using_resources(cf)?;
12908                    }
12909                    FunctionPropertyKind::Language => {
12910                        if !cf.language_first {
12911                            // Only output here if not already output above
12912                            if let Some(lang) = &cf.language {
12913                                // Only BigQuery uses multiline formatting
12914                                let use_multiline = self.config.pretty
12915                                    && matches!(
12916                                        self.config.dialect,
12917                                        Some(crate::dialects::DialectType::BigQuery)
12918                                    );
12919                                if use_multiline {
12920                                    self.write_newline();
12921                                } else {
12922                                    self.write_space();
12923                                }
12924                                self.write_keyword("LANGUAGE");
12925                                self.write_space();
12926                                self.write(lang);
12927                            }
12928                        }
12929                    }
12930                    FunctionPropertyKind::Determinism => {
12931                        self.generate_function_determinism(cf)?;
12932                    }
12933                    FunctionPropertyKind::NullInput => {
12934                        self.generate_function_null_input(cf)?;
12935                    }
12936                    FunctionPropertyKind::Security => {
12937                        self.generate_function_security(cf)?;
12938                    }
12939                    FunctionPropertyKind::SqlDataAccess => {
12940                        if !cf.language_first {
12941                            // Only output here if not already output above
12942                            self.generate_function_sql_data_access(cf)?;
12943                        }
12944                    }
12945                    FunctionPropertyKind::Options => {
12946                        if !cf.options.is_empty() {
12947                            self.write_space();
12948                            self.generate_options_clause(&cf.options)?;
12949                        }
12950                    }
12951                    FunctionPropertyKind::Environment => {
12952                        if !cf.environment.is_empty() {
12953                            self.write_space();
12954                            self.generate_environment_clause(&cf.environment)?;
12955                        }
12956                    }
12957                    FunctionPropertyKind::Handler => {
12958                        if let Some(ref h) = cf.handler {
12959                            self.write_space();
12960                            self.write_keyword("HANDLER");
12961                            if cf.handler_uses_eq {
12962                                self.write(" = ");
12963                            } else {
12964                                self.write_space();
12965                            }
12966                            self.write("'");
12967                            self.write(h);
12968                            self.write("'");
12969                        }
12970                    }
12971                    FunctionPropertyKind::RuntimeVersion => {
12972                        if let Some(ref runtime_version) = cf.runtime_version {
12973                            self.write_space();
12974                            self.write_keyword("RUNTIME_VERSION");
12975                            self.write("='");
12976                            self.write(runtime_version);
12977                            self.write("'");
12978                        }
12979                    }
12980                    FunctionPropertyKind::Packages => {
12981                        if let Some(ref packages) = cf.packages {
12982                            self.write_space();
12983                            self.write_keyword("PACKAGES");
12984                            self.write("=(");
12985                            for (i, package) in packages.iter().enumerate() {
12986                                if i > 0 {
12987                                    self.write(", ");
12988                                }
12989                                self.write("'");
12990                                self.write(package);
12991                                self.write("'");
12992                            }
12993                            self.write(")");
12994                        }
12995                    }
12996                    FunctionPropertyKind::ParameterStyle => {
12997                        if let Some(ref ps) = cf.parameter_style {
12998                            self.write_space();
12999                            self.write_keyword("PARAMETER STYLE");
13000                            self.write_space();
13001                            self.write_keyword(ps);
13002                        }
13003                    }
13004                }
13005            }
13006
13007            // Output OPTIONS if not tracked in property_order (legacy)
13008            if !cf.options.is_empty() && !cf.property_order.contains(&FunctionPropertyKind::Options)
13009            {
13010                self.write_space();
13011                self.generate_options_clause(&cf.options)?;
13012            }
13013
13014            // Output ENVIRONMENT if not tracked in property_order (legacy)
13015            if !cf.environment.is_empty()
13016                && !cf
13017                    .property_order
13018                    .contains(&FunctionPropertyKind::Environment)
13019            {
13020                self.write_space();
13021                self.generate_environment_clause(&cf.environment)?;
13022            }
13023        } else {
13024            // Legacy behavior when property_order is empty
13025            // BigQuery: DETERMINISTIC/NOT DETERMINISTIC comes before LANGUAGE
13026            if matches!(
13027                self.config.dialect,
13028                Some(crate::dialects::DialectType::BigQuery)
13029            ) {
13030                self.generate_function_determinism(cf)?;
13031            }
13032
13033            // Only BigQuery uses multiline formatting for CREATE FUNCTION structure
13034            let use_multiline = self.config.pretty
13035                && matches!(
13036                    self.config.dialect,
13037                    Some(crate::dialects::DialectType::BigQuery)
13038                );
13039
13040            if !cf.language_first {
13041                if let Some(lang) = &cf.language {
13042                    if use_multiline {
13043                        self.write_newline();
13044                    } else {
13045                        self.write_space();
13046                    }
13047                    self.write_keyword("LANGUAGE");
13048                    self.write_space();
13049                    self.write(lang);
13050                }
13051
13052                // SQL data access characteristic comes after LANGUAGE
13053                self.generate_function_sql_data_access(cf)?;
13054            }
13055
13056            // For non-BigQuery dialects, output DETERMINISTIC/IMMUTABLE/VOLATILE here
13057            if !matches!(
13058                self.config.dialect,
13059                Some(crate::dialects::DialectType::BigQuery)
13060            ) {
13061                self.generate_function_determinism(cf)?;
13062            }
13063
13064            self.generate_function_null_input(cf)?;
13065            self.generate_function_security(cf)?;
13066            self.generate_function_set_options(cf)?;
13067
13068            // BigQuery: OPTIONS (key=value, ...) - comes before AS
13069            if !cf.options.is_empty() {
13070                self.write_space();
13071                self.generate_options_clause(&cf.options)?;
13072            }
13073
13074            // Databricks: ENVIRONMENT (dependencies = '...', ...) - comes before AS
13075            if !cf.environment.is_empty() {
13076                self.write_space();
13077                self.generate_environment_clause(&cf.environment)?;
13078            }
13079
13080            if let Some(ref h) = cf.handler {
13081                self.write_space();
13082                self.write_keyword("HANDLER");
13083                if cf.handler_uses_eq {
13084                    self.write(" = ");
13085                } else {
13086                    self.write_space();
13087                }
13088                self.write("'");
13089                self.write(h);
13090                self.write("'");
13091            }
13092
13093            if let Some(ref runtime_version) = cf.runtime_version {
13094                self.write_space();
13095                self.write_keyword("RUNTIME_VERSION");
13096                self.write("='");
13097                self.write(runtime_version);
13098                self.write("'");
13099            }
13100
13101            if let Some(ref packages) = cf.packages {
13102                self.write_space();
13103                self.write_keyword("PACKAGES");
13104                self.write("=(");
13105                for (i, package) in packages.iter().enumerate() {
13106                    if i > 0 {
13107                        self.write(", ");
13108                    }
13109                    self.write("'");
13110                    self.write(package);
13111                    self.write("'");
13112                }
13113                self.write(")");
13114            }
13115
13116            self.generate_function_body(cf)?;
13117            self.generate_function_using_resources(cf)?;
13118        }
13119
13120        Ok(())
13121    }
13122
13123    /// Generate SET options for CREATE FUNCTION
13124    fn generate_function_set_options(&mut self, cf: &CreateFunction) -> Result<()> {
13125        for opt in &cf.set_options {
13126            self.write_space();
13127            self.write_keyword("SET");
13128            self.write_space();
13129            self.write(&opt.name);
13130            match &opt.value {
13131                FunctionSetValue::Value { value, use_to } => {
13132                    if *use_to {
13133                        self.write(" TO ");
13134                    } else {
13135                        self.write(" = ");
13136                    }
13137                    self.write(value);
13138                }
13139                FunctionSetValue::FromCurrent => {
13140                    self.write_space();
13141                    self.write_keyword("FROM CURRENT");
13142                }
13143            }
13144        }
13145        Ok(())
13146    }
13147
13148    fn generate_function_using_resources(&mut self, cf: &CreateFunction) -> Result<()> {
13149        if cf.using_resources.is_empty() {
13150            return Ok(());
13151        }
13152
13153        self.write_space();
13154        self.write_keyword("USING");
13155        for resource in &cf.using_resources {
13156            self.write_space();
13157            self.write_keyword(&resource.kind);
13158            self.write_space();
13159            self.generate_string_literal(&resource.uri)?;
13160        }
13161        Ok(())
13162    }
13163
13164    /// Generate function body (AS clause)
13165    fn generate_function_body(&mut self, cf: &CreateFunction) -> Result<()> {
13166        if let Some(body) = &cf.body {
13167            // AS stays on same line as previous content (e.g., LANGUAGE js AS)
13168            self.write_space();
13169            // Only BigQuery uses multiline formatting for CREATE FUNCTION body
13170            let use_multiline = self.config.pretty
13171                && matches!(
13172                    self.config.dialect,
13173                    Some(crate::dialects::DialectType::BigQuery)
13174                );
13175            match body {
13176                FunctionBody::Block(block) => {
13177                    self.write_keyword("AS");
13178                    if matches!(
13179                        self.config.dialect,
13180                        Some(crate::dialects::DialectType::TSQL)
13181                    ) {
13182                        self.write(" BEGIN ");
13183                        self.write(block);
13184                        self.write(" END");
13185                    } else if matches!(
13186                        self.config.dialect,
13187                        Some(crate::dialects::DialectType::PostgreSQL)
13188                    ) {
13189                        self.write(" $$");
13190                        self.write(block);
13191                        self.write("$$");
13192                    } else {
13193                        // Escape content for single-quoted output
13194                        let escaped = self.escape_block_for_single_quote(block);
13195                        // In BigQuery pretty mode, body content goes on new line
13196                        if use_multiline {
13197                            self.write_newline();
13198                        } else {
13199                            self.write(" ");
13200                        }
13201                        self.write("'");
13202                        self.write(&escaped);
13203                        self.write("'");
13204                    }
13205                }
13206                FunctionBody::StringLiteral(s) => {
13207                    self.write_keyword("AS");
13208                    // In BigQuery pretty mode, body content goes on new line
13209                    if use_multiline {
13210                        self.write_newline();
13211                    } else {
13212                        self.write(" ");
13213                    }
13214                    self.write("'");
13215                    self.write(s);
13216                    self.write("'");
13217                }
13218                FunctionBody::Expression(expr) => {
13219                    self.write_keyword("AS");
13220                    self.write_space();
13221                    self.generate_expression(expr)?;
13222                }
13223                FunctionBody::External(name) => {
13224                    self.write_keyword("EXTERNAL NAME");
13225                    self.write(" '");
13226                    self.write(name);
13227                    self.write("'");
13228                }
13229                FunctionBody::Return(expr) => {
13230                    if matches!(
13231                        self.config.dialect,
13232                        Some(crate::dialects::DialectType::DuckDB)
13233                    ) {
13234                        // DuckDB macro syntax: AS [TABLE] expression (no RETURN keyword)
13235                        self.write_keyword("AS");
13236                        self.write_space();
13237                        // Check both returns_table_body marker and return_type = Custom "TABLE"
13238                        let is_table_return = cf.returns_table_body.is_some()
13239                            || matches!(&cf.return_type, Some(crate::expressions::DataType::Custom { ref name }) if name.eq_ignore_ascii_case("TABLE"));
13240                        if is_table_return {
13241                            self.write_keyword("TABLE");
13242                            self.write_space();
13243                        }
13244                        self.generate_expression(expr)?;
13245                    } else {
13246                        if self.config.create_function_return_as {
13247                            self.write_keyword("AS");
13248                            // TSQL pretty: newline between AS and RETURN
13249                            if self.config.pretty
13250                                && matches!(
13251                                    self.config.dialect,
13252                                    Some(crate::dialects::DialectType::TSQL)
13253                                        | Some(crate::dialects::DialectType::Fabric)
13254                                )
13255                            {
13256                                self.write_newline();
13257                            } else {
13258                                self.write_space();
13259                            }
13260                        }
13261                        self.write_keyword("RETURN");
13262                        self.write_space();
13263                        self.generate_expression(expr)?;
13264                    }
13265                }
13266                FunctionBody::Statements(stmts) => {
13267                    self.write_keyword("AS");
13268                    self.write(" BEGIN ");
13269                    for (i, stmt) in stmts.iter().enumerate() {
13270                        if i > 0 {
13271                            self.write(" ");
13272                        }
13273                        self.generate_expression(stmt)?;
13274                        self.write(";");
13275                    }
13276                    self.write(" END");
13277                }
13278                FunctionBody::RawBlock(text) => {
13279                    self.write_newline();
13280                    self.write(text);
13281                }
13282                FunctionBody::DollarQuoted { content, tag } => {
13283                    self.write_keyword("AS");
13284                    self.write(" ");
13285                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13286                    let supports_dollar_quoting = matches!(
13287                        self.config.dialect,
13288                        Some(crate::dialects::DialectType::PostgreSQL)
13289                            | Some(crate::dialects::DialectType::Databricks)
13290                            | Some(crate::dialects::DialectType::Redshift)
13291                            | Some(crate::dialects::DialectType::DuckDB)
13292                    );
13293                    if supports_dollar_quoting {
13294                        // Output in dollar-quoted format
13295                        self.write("$");
13296                        if let Some(t) = tag {
13297                            self.write(t);
13298                        }
13299                        self.write("$");
13300                        self.write(content);
13301                        self.write("$");
13302                        if let Some(t) = tag {
13303                            self.write(t);
13304                        }
13305                        self.write("$");
13306                    } else {
13307                        // Convert to single-quoted string for other dialects
13308                        let escaped = self.escape_block_for_single_quote(content);
13309                        self.write("'");
13310                        self.write(&escaped);
13311                        self.write("'");
13312                    }
13313                }
13314            }
13315        }
13316        Ok(())
13317    }
13318
13319    /// Generate determinism clause (IMMUTABLE/VOLATILE/DETERMINISTIC)
13320    fn generate_function_determinism(&mut self, cf: &CreateFunction) -> Result<()> {
13321        if let Some(det) = cf.deterministic {
13322            self.write_space();
13323            if matches!(
13324                self.config.dialect,
13325                Some(crate::dialects::DialectType::BigQuery)
13326            ) {
13327                // BigQuery uses DETERMINISTIC/NOT DETERMINISTIC
13328                if det {
13329                    self.write_keyword("DETERMINISTIC");
13330                } else {
13331                    self.write_keyword("NOT DETERMINISTIC");
13332                }
13333            } else {
13334                // PostgreSQL and others use IMMUTABLE/VOLATILE
13335                if det {
13336                    self.write_keyword("IMMUTABLE");
13337                } else {
13338                    self.write_keyword("VOLATILE");
13339                }
13340            }
13341        }
13342        Ok(())
13343    }
13344
13345    /// Generate null input handling clause
13346    fn generate_function_null_input(&mut self, cf: &CreateFunction) -> Result<()> {
13347        if let Some(returns_null) = cf.returns_null_on_null_input {
13348            self.write_space();
13349            if returns_null {
13350                if cf.strict {
13351                    self.write_keyword("STRICT");
13352                } else {
13353                    self.write_keyword("RETURNS NULL ON NULL INPUT");
13354                }
13355            } else {
13356                self.write_keyword("CALLED ON NULL INPUT");
13357            }
13358        }
13359        Ok(())
13360    }
13361
13362    /// Generate security clause
13363    fn generate_function_security(&mut self, cf: &CreateFunction) -> Result<()> {
13364        if let Some(security) = &cf.security {
13365            self.write_space();
13366            // MySQL uses SQL SECURITY prefix
13367            if matches!(
13368                self.config.dialect,
13369                Some(crate::dialects::DialectType::MySQL)
13370            ) {
13371                self.write_keyword("SQL SECURITY");
13372            } else {
13373                self.write_keyword("SECURITY");
13374            }
13375            self.write_space();
13376            match security {
13377                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13378                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13379                FunctionSecurity::None => self.write_keyword("NONE"),
13380            }
13381        }
13382        Ok(())
13383    }
13384
13385    /// Generate SQL data access clause
13386    fn generate_function_sql_data_access(&mut self, cf: &CreateFunction) -> Result<()> {
13387        if let Some(sql_data) = &cf.sql_data_access {
13388            self.write_space();
13389            match sql_data {
13390                SqlDataAccess::NoSql => self.write_keyword("NO SQL"),
13391                SqlDataAccess::ContainsSql => self.write_keyword("CONTAINS SQL"),
13392                SqlDataAccess::ReadsSqlData => self.write_keyword("READS SQL DATA"),
13393                SqlDataAccess::ModifiesSqlData => self.write_keyword("MODIFIES SQL DATA"),
13394            }
13395        }
13396        Ok(())
13397    }
13398
13399    fn generate_function_parameters(&mut self, params: &[FunctionParameter]) -> Result<()> {
13400        for (i, param) in params.iter().enumerate() {
13401            if i > 0 {
13402                self.write(", ");
13403            }
13404
13405            if let Some(mode) = &param.mode {
13406                if let Some(text) = &param.mode_text {
13407                    self.write(text);
13408                } else {
13409                    match mode {
13410                        ParameterMode::In => self.write_keyword("IN"),
13411                        ParameterMode::Out => self.write_keyword("OUT"),
13412                        ParameterMode::InOut => self.write_keyword("INOUT"),
13413                        ParameterMode::Variadic => self.write_keyword("VARIADIC"),
13414                    }
13415                }
13416                self.write_space();
13417            }
13418
13419            if let Some(name) = &param.name {
13420                self.generate_identifier(name)?;
13421                // Skip space and type for empty Custom types (e.g., DuckDB macros)
13422                let skip_type =
13423                    matches!(&param.data_type, DataType::Custom { name } if name.is_empty());
13424                if !skip_type {
13425                    self.write_space();
13426                    self.generate_data_type(&param.data_type)?;
13427                }
13428            } else {
13429                self.generate_data_type(&param.data_type)?;
13430            }
13431
13432            if let Some(default) = &param.default {
13433                if self.config.parameter_default_equals {
13434                    self.write(" = ");
13435                } else {
13436                    self.write(" DEFAULT ");
13437                }
13438                self.generate_expression(default)?;
13439            }
13440        }
13441
13442        Ok(())
13443    }
13444
13445    fn generate_drop_function(&mut self, df: &DropFunction) -> Result<()> {
13446        self.write_keyword("DROP FUNCTION");
13447
13448        if df.if_exists {
13449            self.write_space();
13450            self.write_keyword("IF EXISTS");
13451        }
13452
13453        self.write_space();
13454        self.generate_table(&df.name)?;
13455
13456        if let Some(params) = &df.parameters {
13457            self.write(" (");
13458            for (i, dt) in params.iter().enumerate() {
13459                if i > 0 {
13460                    self.write(", ");
13461                }
13462                self.generate_data_type(dt)?;
13463            }
13464            self.write(")");
13465        }
13466
13467        if df.cascade {
13468            self.write_space();
13469            self.write_keyword("CASCADE");
13470        }
13471
13472        Ok(())
13473    }
13474
13475    fn generate_create_procedure(&mut self, cp: &CreateProcedure) -> Result<()> {
13476        self.write_keyword("CREATE");
13477
13478        if cp.or_alter {
13479            self.write_space();
13480            self.write_keyword("OR ALTER");
13481        } else if cp.or_replace {
13482            self.write_space();
13483            self.write_keyword("OR REPLACE");
13484        }
13485
13486        self.write_space();
13487        if cp.use_proc_keyword {
13488            self.write_keyword("PROC");
13489        } else {
13490            self.write_keyword("PROCEDURE");
13491        }
13492
13493        if cp.if_not_exists {
13494            self.write_space();
13495            self.write_keyword("IF NOT EXISTS");
13496        }
13497
13498        self.write_space();
13499        self.generate_table(&cp.name)?;
13500        if cp.has_parens {
13501            self.write("(");
13502            self.generate_function_parameters(&cp.parameters)?;
13503            self.write(")");
13504        } else if !cp.parameters.is_empty() {
13505            // TSQL: unparenthesized parameters
13506            self.write_space();
13507            self.generate_function_parameters(&cp.parameters)?;
13508        }
13509
13510        // RETURNS clause (Snowflake)
13511        if let Some(return_type) = &cp.return_type {
13512            self.write_space();
13513            self.write_keyword("RETURNS");
13514            self.write_space();
13515            self.generate_data_type(return_type)?;
13516        }
13517
13518        // EXECUTE AS clause (Snowflake)
13519        if let Some(execute_as) = &cp.execute_as {
13520            self.write_space();
13521            self.write_keyword("EXECUTE AS");
13522            self.write_space();
13523            self.write_keyword(execute_as);
13524        }
13525
13526        if let Some(lang) = &cp.language {
13527            self.write_space();
13528            self.write_keyword("LANGUAGE");
13529            self.write_space();
13530            self.write(lang);
13531        }
13532
13533        if let Some(security) = &cp.security {
13534            self.write_space();
13535            self.write_keyword("SECURITY");
13536            self.write_space();
13537            match security {
13538                FunctionSecurity::Definer => self.write_keyword("DEFINER"),
13539                FunctionSecurity::Invoker => self.write_keyword("INVOKER"),
13540                FunctionSecurity::None => self.write_keyword("NONE"),
13541            }
13542        }
13543
13544        // TSQL WITH options (ENCRYPTION, RECOMPILE, etc.)
13545        if !cp.with_options.is_empty() {
13546            self.write_space();
13547            self.write_keyword("WITH");
13548            self.write_space();
13549            for (i, opt) in cp.with_options.iter().enumerate() {
13550                if i > 0 {
13551                    self.write(", ");
13552                }
13553                self.write(opt);
13554            }
13555        }
13556
13557        if let Some(body) = &cp.body {
13558            self.write_space();
13559            match body {
13560                FunctionBody::Block(block) => {
13561                    self.write_keyword("AS");
13562                    if matches!(
13563                        self.config.dialect,
13564                        Some(crate::dialects::DialectType::TSQL)
13565                    ) {
13566                        self.write(" BEGIN ");
13567                        self.write(block);
13568                        self.write(" END");
13569                    } else if matches!(
13570                        self.config.dialect,
13571                        Some(crate::dialects::DialectType::PostgreSQL)
13572                    ) {
13573                        self.write(" $$");
13574                        self.write(block);
13575                        self.write("$$");
13576                    } else {
13577                        // Escape content for single-quoted output
13578                        let escaped = self.escape_block_for_single_quote(block);
13579                        self.write(" '");
13580                        self.write(&escaped);
13581                        self.write("'");
13582                    }
13583                }
13584                FunctionBody::StringLiteral(s) => {
13585                    self.write_keyword("AS");
13586                    self.write(" '");
13587                    self.write(s);
13588                    self.write("'");
13589                }
13590                FunctionBody::Expression(expr) => {
13591                    self.write_keyword("AS");
13592                    self.write_space();
13593                    self.generate_expression(expr)?;
13594                }
13595                FunctionBody::External(name) => {
13596                    self.write_keyword("EXTERNAL NAME");
13597                    self.write(" '");
13598                    self.write(name);
13599                    self.write("'");
13600                }
13601                FunctionBody::Return(expr) => {
13602                    self.write_keyword("RETURN");
13603                    self.write_space();
13604                    self.generate_expression(expr)?;
13605                }
13606                FunctionBody::Statements(stmts) => {
13607                    self.write_keyword("AS");
13608                    self.write(" BEGIN ");
13609                    for (i, stmt) in stmts.iter().enumerate() {
13610                        if i > 0 {
13611                            self.write(" ");
13612                        }
13613                        self.generate_expression(stmt)?;
13614                        self.write(";");
13615                    }
13616                    self.write(" END");
13617                }
13618                FunctionBody::RawBlock(text) => {
13619                    self.write_newline();
13620                    self.write(text);
13621                }
13622                FunctionBody::DollarQuoted { content, tag } => {
13623                    self.write_keyword("AS");
13624                    self.write(" ");
13625                    // Dialects that support dollar-quoted strings: PostgreSQL, Databricks, Redshift, DuckDB
13626                    let supports_dollar_quoting = matches!(
13627                        self.config.dialect,
13628                        Some(crate::dialects::DialectType::PostgreSQL)
13629                            | Some(crate::dialects::DialectType::Databricks)
13630                            | Some(crate::dialects::DialectType::Redshift)
13631                            | Some(crate::dialects::DialectType::DuckDB)
13632                    );
13633                    if supports_dollar_quoting {
13634                        // Output in dollar-quoted format
13635                        self.write("$");
13636                        if let Some(t) = tag {
13637                            self.write(t);
13638                        }
13639                        self.write("$");
13640                        self.write(content);
13641                        self.write("$");
13642                        if let Some(t) = tag {
13643                            self.write(t);
13644                        }
13645                        self.write("$");
13646                    } else {
13647                        // Convert to single-quoted string for other dialects
13648                        let escaped = self.escape_block_for_single_quote(content);
13649                        self.write("'");
13650                        self.write(&escaped);
13651                        self.write("'");
13652                    }
13653                }
13654            }
13655        }
13656
13657        Ok(())
13658    }
13659
13660    fn generate_drop_procedure(&mut self, dp: &DropProcedure) -> Result<()> {
13661        self.write_keyword("DROP PROCEDURE");
13662
13663        if dp.if_exists {
13664            self.write_space();
13665            self.write_keyword("IF EXISTS");
13666        }
13667
13668        self.write_space();
13669        self.generate_table(&dp.name)?;
13670
13671        if let Some(params) = &dp.parameters {
13672            self.write(" (");
13673            for (i, dt) in params.iter().enumerate() {
13674                if i > 0 {
13675                    self.write(", ");
13676                }
13677                self.generate_data_type(dt)?;
13678            }
13679            self.write(")");
13680        }
13681
13682        if dp.cascade {
13683            self.write_space();
13684            self.write_keyword("CASCADE");
13685        }
13686
13687        Ok(())
13688    }
13689
13690    fn generate_create_sequence(&mut self, cs: &CreateSequence) -> Result<()> {
13691        self.write_keyword("CREATE");
13692
13693        if cs.or_replace {
13694            self.write_space();
13695            self.write_keyword("OR REPLACE");
13696        }
13697
13698        if cs.temporary {
13699            self.write_space();
13700            self.write_keyword("TEMPORARY");
13701        }
13702
13703        self.write_space();
13704        self.write_keyword("SEQUENCE");
13705
13706        if cs.if_not_exists {
13707            self.write_space();
13708            self.write_keyword("IF NOT EXISTS");
13709        }
13710
13711        self.write_space();
13712        self.generate_table(&cs.name)?;
13713
13714        // Output AS <type> if present
13715        if let Some(as_type) = &cs.as_type {
13716            self.write_space();
13717            self.write_keyword("AS");
13718            self.write_space();
13719            self.generate_data_type(as_type)?;
13720        }
13721
13722        // Output COMMENT first (Snowflake convention: COMMENT comes before other properties)
13723        if let Some(comment) = &cs.comment {
13724            self.write_space();
13725            self.write_keyword("COMMENT");
13726            self.write("=");
13727            self.generate_string_literal(comment)?;
13728        }
13729
13730        // If property_order is available, use it to preserve original order
13731        if !cs.property_order.is_empty() {
13732            for prop in &cs.property_order {
13733                match prop {
13734                    SeqPropKind::Start => {
13735                        if let Some(start) = cs.start {
13736                            self.write_space();
13737                            self.write_keyword("START WITH");
13738                            self.write(&format!(" {}", start));
13739                        }
13740                    }
13741                    SeqPropKind::Increment => {
13742                        if let Some(inc) = cs.increment {
13743                            self.write_space();
13744                            self.write_keyword("INCREMENT BY");
13745                            self.write(&format!(" {}", inc));
13746                        }
13747                    }
13748                    SeqPropKind::Minvalue => {
13749                        if let Some(min) = &cs.minvalue {
13750                            self.write_space();
13751                            match min {
13752                                SequenceBound::Value(v) => {
13753                                    self.write_keyword("MINVALUE");
13754                                    self.write(&format!(" {}", v));
13755                                }
13756                                SequenceBound::None => {
13757                                    self.write_keyword("NO MINVALUE");
13758                                }
13759                            }
13760                        }
13761                    }
13762                    SeqPropKind::Maxvalue => {
13763                        if let Some(max) = &cs.maxvalue {
13764                            self.write_space();
13765                            match max {
13766                                SequenceBound::Value(v) => {
13767                                    self.write_keyword("MAXVALUE");
13768                                    self.write(&format!(" {}", v));
13769                                }
13770                                SequenceBound::None => {
13771                                    self.write_keyword("NO MAXVALUE");
13772                                }
13773                            }
13774                        }
13775                    }
13776                    SeqPropKind::Cache => {
13777                        if let Some(cache) = cs.cache {
13778                            self.write_space();
13779                            self.write_keyword("CACHE");
13780                            self.write(&format!(" {}", cache));
13781                        }
13782                    }
13783                    SeqPropKind::NoCache => {
13784                        self.write_space();
13785                        self.write_keyword("NO CACHE");
13786                    }
13787                    SeqPropKind::NoCacheWord => {
13788                        self.write_space();
13789                        self.write_keyword("NOCACHE");
13790                    }
13791                    SeqPropKind::Cycle => {
13792                        self.write_space();
13793                        self.write_keyword("CYCLE");
13794                    }
13795                    SeqPropKind::NoCycle => {
13796                        self.write_space();
13797                        self.write_keyword("NO CYCLE");
13798                    }
13799                    SeqPropKind::NoCycleWord => {
13800                        self.write_space();
13801                        self.write_keyword("NOCYCLE");
13802                    }
13803                    SeqPropKind::OwnedBy => {
13804                        // Skip OWNED BY NONE (it's a no-op)
13805                        if !cs.owned_by_none {
13806                            if let Some(owned) = &cs.owned_by {
13807                                self.write_space();
13808                                self.write_keyword("OWNED BY");
13809                                self.write_space();
13810                                self.generate_table(owned)?;
13811                            }
13812                        }
13813                    }
13814                    SeqPropKind::Order => {
13815                        self.write_space();
13816                        self.write_keyword("ORDER");
13817                    }
13818                    SeqPropKind::NoOrder => {
13819                        self.write_space();
13820                        self.write_keyword("NOORDER");
13821                    }
13822                    SeqPropKind::Comment => {
13823                        // COMMENT is output above, before property_order iteration
13824                    }
13825                    SeqPropKind::Sharing => {
13826                        if let Some(val) = &cs.sharing {
13827                            self.write_space();
13828                            self.write(&format!("SHARING={}", val));
13829                        }
13830                    }
13831                    SeqPropKind::Keep => {
13832                        self.write_space();
13833                        self.write_keyword("KEEP");
13834                    }
13835                    SeqPropKind::NoKeep => {
13836                        self.write_space();
13837                        self.write_keyword("NOKEEP");
13838                    }
13839                    SeqPropKind::Scale => {
13840                        self.write_space();
13841                        self.write_keyword("SCALE");
13842                        if let Some(modifier) = &cs.scale_modifier {
13843                            if !modifier.is_empty() {
13844                                self.write_space();
13845                                self.write_keyword(modifier);
13846                            }
13847                        }
13848                    }
13849                    SeqPropKind::NoScale => {
13850                        self.write_space();
13851                        self.write_keyword("NOSCALE");
13852                    }
13853                    SeqPropKind::Shard => {
13854                        self.write_space();
13855                        self.write_keyword("SHARD");
13856                        if let Some(modifier) = &cs.shard_modifier {
13857                            if !modifier.is_empty() {
13858                                self.write_space();
13859                                self.write_keyword(modifier);
13860                            }
13861                        }
13862                    }
13863                    SeqPropKind::NoShard => {
13864                        self.write_space();
13865                        self.write_keyword("NOSHARD");
13866                    }
13867                    SeqPropKind::Session => {
13868                        self.write_space();
13869                        self.write_keyword("SESSION");
13870                    }
13871                    SeqPropKind::Global => {
13872                        self.write_space();
13873                        self.write_keyword("GLOBAL");
13874                    }
13875                    SeqPropKind::NoMinvalueWord => {
13876                        self.write_space();
13877                        self.write_keyword("NOMINVALUE");
13878                    }
13879                    SeqPropKind::NoMaxvalueWord => {
13880                        self.write_space();
13881                        self.write_keyword("NOMAXVALUE");
13882                    }
13883                }
13884            }
13885        } else {
13886            // Fallback: default order for backwards compatibility
13887            if let Some(inc) = cs.increment {
13888                self.write_space();
13889                self.write_keyword("INCREMENT BY");
13890                self.write(&format!(" {}", inc));
13891            }
13892
13893            if let Some(min) = &cs.minvalue {
13894                self.write_space();
13895                match min {
13896                    SequenceBound::Value(v) => {
13897                        self.write_keyword("MINVALUE");
13898                        self.write(&format!(" {}", v));
13899                    }
13900                    SequenceBound::None => {
13901                        self.write_keyword("NO MINVALUE");
13902                    }
13903                }
13904            }
13905
13906            if let Some(max) = &cs.maxvalue {
13907                self.write_space();
13908                match max {
13909                    SequenceBound::Value(v) => {
13910                        self.write_keyword("MAXVALUE");
13911                        self.write(&format!(" {}", v));
13912                    }
13913                    SequenceBound::None => {
13914                        self.write_keyword("NO MAXVALUE");
13915                    }
13916                }
13917            }
13918
13919            if let Some(start) = cs.start {
13920                self.write_space();
13921                self.write_keyword("START WITH");
13922                self.write(&format!(" {}", start));
13923            }
13924
13925            if let Some(cache) = cs.cache {
13926                self.write_space();
13927                self.write_keyword("CACHE");
13928                self.write(&format!(" {}", cache));
13929            }
13930
13931            if cs.cycle {
13932                self.write_space();
13933                self.write_keyword("CYCLE");
13934            }
13935
13936            if let Some(owned) = &cs.owned_by {
13937                self.write_space();
13938                self.write_keyword("OWNED BY");
13939                self.write_space();
13940                self.generate_table(owned)?;
13941            }
13942        }
13943
13944        Ok(())
13945    }
13946
13947    fn generate_drop_sequence(&mut self, ds: &DropSequence) -> Result<()> {
13948        self.write_keyword("DROP SEQUENCE");
13949
13950        if ds.if_exists {
13951            self.write_space();
13952            self.write_keyword("IF EXISTS");
13953        }
13954
13955        self.write_space();
13956        self.generate_table(&ds.name)?;
13957
13958        if ds.cascade {
13959            self.write_space();
13960            self.write_keyword("CASCADE");
13961        }
13962
13963        Ok(())
13964    }
13965
13966    fn generate_alter_sequence(&mut self, als: &AlterSequence) -> Result<()> {
13967        self.write_keyword("ALTER SEQUENCE");
13968
13969        if als.if_exists {
13970            self.write_space();
13971            self.write_keyword("IF EXISTS");
13972        }
13973
13974        self.write_space();
13975        self.generate_table(&als.name)?;
13976
13977        if let Some(inc) = als.increment {
13978            self.write_space();
13979            self.write_keyword("INCREMENT BY");
13980            self.write(&format!(" {}", inc));
13981        }
13982
13983        if let Some(min) = &als.minvalue {
13984            self.write_space();
13985            match min {
13986                SequenceBound::Value(v) => {
13987                    self.write_keyword("MINVALUE");
13988                    self.write(&format!(" {}", v));
13989                }
13990                SequenceBound::None => {
13991                    self.write_keyword("NO MINVALUE");
13992                }
13993            }
13994        }
13995
13996        if let Some(max) = &als.maxvalue {
13997            self.write_space();
13998            match max {
13999                SequenceBound::Value(v) => {
14000                    self.write_keyword("MAXVALUE");
14001                    self.write(&format!(" {}", v));
14002                }
14003                SequenceBound::None => {
14004                    self.write_keyword("NO MAXVALUE");
14005                }
14006            }
14007        }
14008
14009        if let Some(start) = als.start {
14010            self.write_space();
14011            self.write_keyword("START WITH");
14012            self.write(&format!(" {}", start));
14013        }
14014
14015        if let Some(restart) = &als.restart {
14016            self.write_space();
14017            self.write_keyword("RESTART");
14018            if let Some(val) = restart {
14019                self.write_keyword(" WITH");
14020                self.write(&format!(" {}", val));
14021            }
14022        }
14023
14024        if let Some(cache) = als.cache {
14025            self.write_space();
14026            self.write_keyword("CACHE");
14027            self.write(&format!(" {}", cache));
14028        }
14029
14030        if let Some(cycle) = als.cycle {
14031            self.write_space();
14032            if cycle {
14033                self.write_keyword("CYCLE");
14034            } else {
14035                self.write_keyword("NO CYCLE");
14036            }
14037        }
14038
14039        if let Some(owned) = &als.owned_by {
14040            self.write_space();
14041            self.write_keyword("OWNED BY");
14042            self.write_space();
14043            if let Some(table) = owned {
14044                self.generate_table(table)?;
14045            } else {
14046                self.write_keyword("NONE");
14047            }
14048        }
14049
14050        Ok(())
14051    }
14052
14053    fn generate_create_trigger(&mut self, ct: &CreateTrigger) -> Result<()> {
14054        self.write_keyword("CREATE");
14055
14056        if ct.or_alter {
14057            self.write_space();
14058            self.write_keyword("OR ALTER");
14059        } else if ct.or_replace {
14060            self.write_space();
14061            self.write_keyword("OR REPLACE");
14062        }
14063
14064        if ct.constraint {
14065            self.write_space();
14066            self.write_keyword("CONSTRAINT");
14067        }
14068
14069        self.write_space();
14070        self.write_keyword("TRIGGER");
14071        self.write_space();
14072        self.generate_identifier(&ct.name)?;
14073
14074        self.write_space();
14075        match ct.timing {
14076            TriggerTiming::Before => self.write_keyword("BEFORE"),
14077            TriggerTiming::After => self.write_keyword("AFTER"),
14078            TriggerTiming::InsteadOf => self.write_keyword("INSTEAD OF"),
14079        }
14080
14081        // Events
14082        for (i, event) in ct.events.iter().enumerate() {
14083            if i > 0 {
14084                self.write_keyword(" OR");
14085            }
14086            self.write_space();
14087            match event {
14088                TriggerEvent::Insert => self.write_keyword("INSERT"),
14089                TriggerEvent::Update(cols) => {
14090                    self.write_keyword("UPDATE");
14091                    if let Some(cols) = cols {
14092                        self.write_space();
14093                        self.write_keyword("OF");
14094                        for (j, col) in cols.iter().enumerate() {
14095                            if j > 0 {
14096                                self.write(",");
14097                            }
14098                            self.write_space();
14099                            self.generate_identifier(col)?;
14100                        }
14101                    }
14102                }
14103                TriggerEvent::Delete => self.write_keyword("DELETE"),
14104                TriggerEvent::Truncate => self.write_keyword("TRUNCATE"),
14105            }
14106        }
14107
14108        self.write_space();
14109        self.write_keyword("ON");
14110        self.write_space();
14111        self.generate_table(&ct.table)?;
14112
14113        // Referencing clause
14114        if let Some(ref_clause) = &ct.referencing {
14115            self.write_space();
14116            self.write_keyword("REFERENCING");
14117            if let Some(old_table) = &ref_clause.old_table {
14118                self.write_space();
14119                self.write_keyword("OLD TABLE AS");
14120                self.write_space();
14121                self.generate_identifier(old_table)?;
14122            }
14123            if let Some(new_table) = &ref_clause.new_table {
14124                self.write_space();
14125                self.write_keyword("NEW TABLE AS");
14126                self.write_space();
14127                self.generate_identifier(new_table)?;
14128            }
14129            if let Some(old_row) = &ref_clause.old_row {
14130                self.write_space();
14131                self.write_keyword("OLD ROW AS");
14132                self.write_space();
14133                self.generate_identifier(old_row)?;
14134            }
14135            if let Some(new_row) = &ref_clause.new_row {
14136                self.write_space();
14137                self.write_keyword("NEW ROW AS");
14138                self.write_space();
14139                self.generate_identifier(new_row)?;
14140            }
14141        }
14142
14143        // Deferrable options for constraint triggers (must come before FOR EACH)
14144        if let Some(deferrable) = ct.deferrable {
14145            self.write_space();
14146            if deferrable {
14147                self.write_keyword("DEFERRABLE");
14148            } else {
14149                self.write_keyword("NOT DEFERRABLE");
14150            }
14151        }
14152
14153        if let Some(initially) = ct.initially_deferred {
14154            self.write_space();
14155            self.write_keyword("INITIALLY");
14156            self.write_space();
14157            if initially {
14158                self.write_keyword("DEFERRED");
14159            } else {
14160                self.write_keyword("IMMEDIATE");
14161            }
14162        }
14163
14164        if let Some(for_each) = ct.for_each {
14165            self.write_space();
14166            self.write_keyword("FOR EACH");
14167            self.write_space();
14168            match for_each {
14169                TriggerForEach::Row => self.write_keyword("ROW"),
14170                TriggerForEach::Statement => self.write_keyword("STATEMENT"),
14171            }
14172        }
14173
14174        // When clause
14175        if let Some(when) = &ct.when {
14176            self.write_space();
14177            self.write_keyword("WHEN");
14178            if ct.when_paren {
14179                self.write(" (");
14180                self.generate_expression(when)?;
14181                self.write(")");
14182            } else {
14183                self.write_space();
14184                self.generate_expression(when)?;
14185            }
14186        }
14187
14188        // Body
14189        self.write_space();
14190        match &ct.body {
14191            TriggerBody::Execute { function, args } => {
14192                self.write_keyword("EXECUTE FUNCTION");
14193                self.write_space();
14194                self.generate_table(function)?;
14195                self.write("(");
14196                for (i, arg) in args.iter().enumerate() {
14197                    if i > 0 {
14198                        self.write(", ");
14199                    }
14200                    self.generate_expression(arg)?;
14201                }
14202                self.write(")");
14203            }
14204            TriggerBody::Block(block) => {
14205                self.write_keyword("BEGIN");
14206                self.write_space();
14207                self.write(block);
14208                self.write_space();
14209                self.write_keyword("END");
14210            }
14211        }
14212
14213        Ok(())
14214    }
14215
14216    fn generate_drop_trigger(&mut self, dt: &DropTrigger) -> Result<()> {
14217        self.write_keyword("DROP TRIGGER");
14218
14219        if dt.if_exists {
14220            self.write_space();
14221            self.write_keyword("IF EXISTS");
14222        }
14223
14224        self.write_space();
14225        self.generate_identifier(&dt.name)?;
14226
14227        if let Some(table) = &dt.table {
14228            self.write_space();
14229            self.write_keyword("ON");
14230            self.write_space();
14231            self.generate_table(table)?;
14232        }
14233
14234        if dt.cascade {
14235            self.write_space();
14236            self.write_keyword("CASCADE");
14237        }
14238
14239        Ok(())
14240    }
14241
14242    fn generate_create_type(&mut self, ct: &CreateType) -> Result<()> {
14243        self.write_keyword("CREATE TYPE");
14244
14245        if ct.if_not_exists {
14246            self.write_space();
14247            self.write_keyword("IF NOT EXISTS");
14248        }
14249
14250        self.write_space();
14251        self.generate_table(&ct.name)?;
14252
14253        if let TypeDefinition::Base {
14254            input,
14255            output,
14256            internallength,
14257        } = &ct.definition
14258        {
14259            if input.is_empty() && output.is_empty() && internallength.is_none() {
14260                return Ok(());
14261            }
14262        }
14263
14264        self.write_space();
14265        self.write_keyword("AS");
14266        self.write_space();
14267
14268        match &ct.definition {
14269            TypeDefinition::Enum(values) => {
14270                self.write_keyword("ENUM");
14271                self.write(" (");
14272                for (i, val) in values.iter().enumerate() {
14273                    if i > 0 {
14274                        self.write(", ");
14275                    }
14276                    self.write(&format!("'{}'", val));
14277                }
14278                self.write(")");
14279            }
14280            TypeDefinition::Composite(attrs) => {
14281                self.write("(");
14282                for (i, attr) in attrs.iter().enumerate() {
14283                    if i > 0 {
14284                        self.write(", ");
14285                    }
14286                    self.generate_identifier(&attr.name)?;
14287                    self.write_space();
14288                    self.generate_data_type(&attr.data_type)?;
14289                    if let Some(collate) = &attr.collate {
14290                        self.write_space();
14291                        self.write_keyword("COLLATE");
14292                        self.write_space();
14293                        self.generate_identifier(collate)?;
14294                    }
14295                }
14296                self.write(")");
14297            }
14298            TypeDefinition::Range {
14299                subtype,
14300                subtype_diff,
14301                canonical,
14302            } => {
14303                self.write_keyword("RANGE");
14304                self.write(" (");
14305                if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
14306                    self.write("subtype");
14307                } else {
14308                    self.write_keyword("SUBTYPE");
14309                }
14310                self.write(" = ");
14311                self.generate_data_type(subtype)?;
14312                if let Some(diff) = subtype_diff {
14313                    self.write(", ");
14314                    if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
14315                        self.write("subtype_diff");
14316                    } else {
14317                        self.write_keyword("SUBTYPE_DIFF");
14318                    }
14319                    self.write(" = ");
14320                    self.write(diff);
14321                }
14322                if let Some(canon) = canonical {
14323                    self.write(", ");
14324                    if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
14325                        self.write("canonical");
14326                    } else {
14327                        self.write_keyword("CANONICAL");
14328                    }
14329                    self.write(" = ");
14330                    self.write(canon);
14331                }
14332                self.write(")");
14333            }
14334            TypeDefinition::Base {
14335                input,
14336                output,
14337                internallength,
14338            } => {
14339                self.write("(");
14340                self.write_keyword("INPUT");
14341                self.write(" = ");
14342                self.write(input);
14343                self.write(", ");
14344                self.write_keyword("OUTPUT");
14345                self.write(" = ");
14346                self.write(output);
14347                if let Some(len) = internallength {
14348                    self.write(", ");
14349                    self.write_keyword("INTERNALLENGTH");
14350                    self.write(" = ");
14351                    self.write(&len.to_string());
14352                }
14353                self.write(")");
14354            }
14355            TypeDefinition::Domain {
14356                base_type,
14357                default,
14358                constraints,
14359            } => {
14360                self.generate_data_type(base_type)?;
14361                if let Some(def) = default {
14362                    self.write_space();
14363                    self.write_keyword("DEFAULT");
14364                    self.write_space();
14365                    self.generate_expression(def)?;
14366                }
14367                for constr in constraints {
14368                    self.write_space();
14369                    if let Some(name) = &constr.name {
14370                        self.write_keyword("CONSTRAINT");
14371                        self.write_space();
14372                        self.generate_identifier(name)?;
14373                        self.write_space();
14374                    }
14375                    self.write_keyword("CHECK");
14376                    self.write(" (");
14377                    self.generate_expression(&constr.check)?;
14378                    self.write(")");
14379                }
14380            }
14381        }
14382
14383        Ok(())
14384    }
14385
14386    fn generate_create_task(&mut self, task: &crate::expressions::CreateTask) -> Result<()> {
14387        self.write_keyword("CREATE");
14388        if task.or_replace {
14389            self.write_space();
14390            self.write_keyword("OR REPLACE");
14391        }
14392        self.write_space();
14393        self.write_keyword("TASK");
14394        if task.if_not_exists {
14395            self.write_space();
14396            self.write_keyword("IF NOT EXISTS");
14397        }
14398        self.write_space();
14399        self.write(&task.name);
14400        if !task.properties.is_empty() {
14401            // Properties already include leading whitespace from tokens_to_sql
14402            if !task.properties.starts_with('\n') && !task.properties.starts_with(' ') {
14403                self.write_space();
14404            }
14405            self.write(&task.properties);
14406        }
14407        self.write_space();
14408        self.write_keyword("AS");
14409        self.write_space();
14410        self.generate_expression(&task.body)?;
14411        Ok(())
14412    }
14413
14414    fn generate_try_catch(&mut self, try_catch: &TryCatch) -> Result<()> {
14415        self.write_keyword("BEGIN TRY");
14416        self.generate_tsql_block_statements(&try_catch.try_body)?;
14417        self.write_keyword("END TRY");
14418
14419        if let Some(catch_body) = &try_catch.catch_body {
14420            if self.config.pretty {
14421                self.write_newline();
14422                self.write_indent();
14423            } else {
14424                self.write_space();
14425            }
14426            self.write_keyword("BEGIN CATCH");
14427            self.generate_tsql_block_statements(catch_body)?;
14428            self.write_keyword("END CATCH");
14429        }
14430
14431        Ok(())
14432    }
14433
14434    fn generate_tsql_block_statements(&mut self, statements: &[Expression]) -> Result<()> {
14435        if statements.is_empty() {
14436            self.write_space();
14437            return Ok(());
14438        }
14439
14440        if self.config.pretty {
14441            self.indent_level += 1;
14442            for stmt in statements {
14443                self.write_newline();
14444                self.write_indent();
14445                self.generate_expression(stmt)?;
14446                self.write(";");
14447            }
14448            self.indent_level -= 1;
14449            self.write_newline();
14450            self.write_indent();
14451        } else {
14452            self.write_space();
14453            for (i, stmt) in statements.iter().enumerate() {
14454                if i > 0 {
14455                    self.write_space();
14456                }
14457                self.generate_expression(stmt)?;
14458                self.write(";");
14459            }
14460            self.write_space();
14461        }
14462
14463        Ok(())
14464    }
14465
14466    fn generate_drop_type(&mut self, dt: &DropType) -> Result<()> {
14467        self.write_keyword("DROP TYPE");
14468
14469        if dt.if_exists {
14470            self.write_space();
14471            self.write_keyword("IF EXISTS");
14472        }
14473
14474        self.write_space();
14475        self.generate_table(&dt.name)?;
14476
14477        if dt.cascade {
14478            self.write_space();
14479            self.write_keyword("CASCADE");
14480        }
14481
14482        Ok(())
14483    }
14484
14485    fn generate_describe(&mut self, d: &Describe) -> Result<()> {
14486        // Athena: DESCRIBE uses Hive engine (backticks)
14487        let saved_athena_hive_context = self.athena_hive_context;
14488        if matches!(
14489            self.config.dialect,
14490            Some(crate::dialects::DialectType::Athena)
14491        ) {
14492            self.athena_hive_context = true;
14493        }
14494
14495        // Output leading comments before DESCRIBE
14496        for comment in &d.leading_comments {
14497            self.write_formatted_comment(comment);
14498            self.write(" ");
14499        }
14500
14501        self.write_keyword("DESCRIBE");
14502
14503        if d.extended {
14504            self.write_space();
14505            self.write_keyword("EXTENDED");
14506        } else if d.formatted {
14507            self.write_space();
14508            self.write_keyword("FORMATTED");
14509        }
14510
14511        // Output style like ANALYZE, HISTORY
14512        if let Some(ref style) = d.style {
14513            self.write_space();
14514            self.write_keyword(style);
14515        }
14516
14517        // Handle object kind (TABLE, VIEW) based on dialect
14518        let should_output_kind = match self.config.dialect {
14519            // Spark doesn't use TABLE/VIEW after DESCRIBE
14520            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
14521                false
14522            }
14523            // Snowflake always includes TABLE
14524            Some(DialectType::Snowflake) => true,
14525            _ => d.kind.is_some(),
14526        };
14527        if should_output_kind {
14528            if let Some(ref kind) = d.kind {
14529                self.write_space();
14530                self.write_keyword(kind);
14531            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
14532                self.write_space();
14533                self.write_keyword("TABLE");
14534            }
14535        }
14536
14537        self.write_space();
14538        self.generate_expression(&d.target)?;
14539
14540        // Output parenthesized parameter types for PROCEDURE/FUNCTION
14541        if !d.params.is_empty() {
14542            self.write("(");
14543            for (i, param) in d.params.iter().enumerate() {
14544                if i > 0 {
14545                    self.write(", ");
14546                }
14547                self.write(param);
14548            }
14549            self.write(")");
14550        }
14551
14552        // Output PARTITION clause if present (the Partition expression outputs its own PARTITION keyword)
14553        if let Some(ref partition) = d.partition {
14554            self.write_space();
14555            self.generate_expression(partition)?;
14556        }
14557
14558        // Databricks: AS JSON
14559        if d.as_json {
14560            self.write_space();
14561            self.write_keyword("AS JSON");
14562        }
14563
14564        // Output properties like type=stage
14565        for (name, value) in &d.properties {
14566            self.write_space();
14567            self.write(name);
14568            self.write("=");
14569            self.write(value);
14570        }
14571
14572        // Restore Athena Hive context
14573        self.athena_hive_context = saved_athena_hive_context;
14574
14575        Ok(())
14576    }
14577
14578    /// Generate SHOW statement (Snowflake, MySQL, etc.)
14579    /// SHOW [TERSE] <object_type> [HISTORY] [LIKE pattern] [IN <scope>] [STARTS WITH pattern] [LIMIT n] [FROM object]
14580    fn generate_show(&mut self, s: &Show) -> Result<()> {
14581        self.write_keyword("SHOW");
14582        self.write_space();
14583
14584        // TERSE keyword - but not for PRIMARY KEYS, UNIQUE KEYS, IMPORTED KEYS
14585        // where TERSE is syntactically valid but has no effect on output
14586        let show_terse = s.terse
14587            && !matches!(
14588                s.this.as_str(),
14589                "PRIMARY KEYS" | "UNIQUE KEYS" | "IMPORTED KEYS"
14590            );
14591        if show_terse {
14592            self.write_keyword("TERSE");
14593            self.write_space();
14594        }
14595
14596        // Object type (USERS, TABLES, DATABASES, etc.)
14597        self.write_keyword(&s.this);
14598
14599        // Target identifier (MySQL: engine name in SHOW ENGINE, preserved case)
14600        if let Some(ref target_expr) = s.target {
14601            self.write_space();
14602            self.generate_expression(target_expr)?;
14603        }
14604
14605        // HISTORY keyword
14606        if s.history {
14607            self.write_space();
14608            self.write_keyword("HISTORY");
14609        }
14610
14611        // FOR target (MySQL: SHOW GRANTS FOR foo, SHOW PROFILE ... FOR QUERY 5)
14612        if let Some(ref for_target) = s.for_target {
14613            self.write_space();
14614            self.write_keyword("FOR");
14615            self.write_space();
14616            self.generate_expression(for_target)?;
14617        }
14618
14619        // Determine ordering based on dialect:
14620        // - Snowflake: LIKE, IN, STARTS WITH, LIMIT, FROM
14621        // - MySQL: IN, FROM, LIKE (when FROM is present)
14622        use crate::dialects::DialectType;
14623        let is_snowflake = matches!(self.config.dialect, Some(DialectType::Snowflake));
14624        let is_mysql = matches!(self.config.dialect, Some(DialectType::MySQL));
14625        let mysql_tables_scope_as_from = is_mysql
14626            && matches!(s.this.as_str(), "TABLES" | "FULL TABLES")
14627            && s.scope_kind.as_deref() == Some("SCHEMA")
14628            && s.scope.is_some()
14629            && s.from.is_none();
14630
14631        if !is_snowflake && s.from.is_some() {
14632            // MySQL ordering: IN, FROM, LIKE
14633
14634            // IN scope_kind [scope]
14635            if let Some(ref scope_kind) = s.scope_kind {
14636                self.write_space();
14637                self.write_keyword("IN");
14638                self.write_space();
14639                self.write_keyword(scope_kind);
14640                if let Some(ref scope) = s.scope {
14641                    self.write_space();
14642                    self.generate_expression(scope)?;
14643                }
14644            } else if let Some(ref scope) = s.scope {
14645                self.write_space();
14646                self.write_keyword("IN");
14647                self.write_space();
14648                self.generate_expression(scope)?;
14649            }
14650
14651            // FROM clause
14652            if let Some(ref from) = s.from {
14653                self.write_space();
14654                self.write_keyword("FROM");
14655                self.write_space();
14656                self.generate_expression(from)?;
14657            }
14658
14659            // Second FROM clause (db name)
14660            if let Some(ref db) = s.db {
14661                self.write_space();
14662                self.write_keyword("FROM");
14663                self.write_space();
14664                self.generate_expression(db)?;
14665            }
14666
14667            // LIKE pattern
14668            if let Some(ref like) = s.like {
14669                self.write_space();
14670                self.write_keyword("LIKE");
14671                self.write_space();
14672                self.generate_expression(like)?;
14673            }
14674        } else {
14675            // Snowflake ordering: LIKE, IN, STARTS WITH, LIMIT, FROM
14676
14677            // LIKE pattern
14678            if let Some(ref like) = s.like {
14679                self.write_space();
14680                self.write_keyword("LIKE");
14681                self.write_space();
14682                self.generate_expression(like)?;
14683            }
14684
14685            // IN scope_kind [scope]
14686            if mysql_tables_scope_as_from {
14687                self.write_space();
14688                self.write_keyword("FROM");
14689                self.write_space();
14690                self.generate_expression(s.scope.as_ref().unwrap())?;
14691            } else if let Some(ref scope_kind) = s.scope_kind {
14692                self.write_space();
14693                self.write_keyword("IN");
14694                self.write_space();
14695                self.write_keyword(scope_kind);
14696                if let Some(ref scope) = s.scope {
14697                    self.write_space();
14698                    self.generate_expression(scope)?;
14699                }
14700            } else if let Some(ref scope) = s.scope {
14701                self.write_space();
14702                self.write_keyword("IN");
14703                self.write_space();
14704                self.generate_expression(scope)?;
14705            }
14706        }
14707
14708        // STARTS WITH pattern
14709        if let Some(ref starts_with) = s.starts_with {
14710            self.write_space();
14711            self.write_keyword("STARTS WITH");
14712            self.write_space();
14713            self.generate_expression(starts_with)?;
14714        }
14715
14716        // LIMIT clause
14717        if let Some(ref limit) = s.limit {
14718            self.write_space();
14719            self.generate_limit(limit)?;
14720        }
14721
14722        // FROM clause (for Snowflake, FROM comes after STARTS WITH and LIMIT)
14723        if is_snowflake {
14724            if let Some(ref from) = s.from {
14725                self.write_space();
14726                self.write_keyword("FROM");
14727                self.write_space();
14728                self.generate_expression(from)?;
14729            }
14730        }
14731
14732        // WHERE clause (MySQL: SHOW STATUS WHERE condition)
14733        if let Some(ref where_clause) = s.where_clause {
14734            self.write_space();
14735            self.write_keyword("WHERE");
14736            self.write_space();
14737            self.generate_expression(where_clause)?;
14738        }
14739
14740        // MUTEX/STATUS suffix (MySQL: SHOW ENGINE foo STATUS/MUTEX)
14741        if let Some(is_mutex) = s.mutex {
14742            self.write_space();
14743            if is_mutex {
14744                self.write_keyword("MUTEX");
14745            } else {
14746                self.write_keyword("STATUS");
14747            }
14748        }
14749
14750        // WITH PRIVILEGES clause (Snowflake: SHOW ... WITH PRIVILEGES USAGE, MODIFY)
14751        if !s.privileges.is_empty() {
14752            self.write_space();
14753            self.write_keyword("WITH PRIVILEGES");
14754            self.write_space();
14755            for (i, priv_name) in s.privileges.iter().enumerate() {
14756                if i > 0 {
14757                    self.write(", ");
14758                }
14759                self.write_keyword(priv_name);
14760            }
14761        }
14762
14763        Ok(())
14764    }
14765
14766    // ==================== End DDL Generation ====================
14767
14768    fn generate_literal(&mut self, lit: &Literal) -> Result<()> {
14769        use crate::dialects::DialectType;
14770        match lit {
14771            Literal::String(s) => {
14772                self.generate_string_literal(s)?;
14773            }
14774            Literal::Number(n) => {
14775                if matches!(self.config.dialect, Some(DialectType::MySQL))
14776                    && n.len() > 2
14777                    && (n.starts_with("0x") || n.starts_with("0X"))
14778                    && !n[2..].chars().all(|c| c.is_ascii_hexdigit())
14779                {
14780                    return self.generate_identifier(&Identifier {
14781                        name: n.clone(),
14782                        quoted: true,
14783                        trailing_comments: Vec::new(),
14784                        span: None,
14785                    });
14786                }
14787                // Strip underscore digit separators (e.g., 1_000_000 -> 1000000)
14788                // for dialects that don't support them (MySQL interprets as identifier).
14789                // ClickHouse, DuckDB, PostgreSQL, and Hive/Spark/Databricks support them.
14790                let n = if n.contains('_')
14791                    && !matches!(
14792                        self.config.dialect,
14793                        Some(DialectType::ClickHouse)
14794                            | Some(DialectType::DuckDB)
14795                            | Some(DialectType::PostgreSQL)
14796                            | Some(DialectType::Hive)
14797                            | Some(DialectType::Spark)
14798                            | Some(DialectType::Databricks)
14799                    ) {
14800                    std::borrow::Cow::Owned(n.replace('_', ""))
14801                } else {
14802                    std::borrow::Cow::Borrowed(n.as_str())
14803                };
14804                // Normalize numbers starting with decimal point to have leading zero
14805                // e.g., .25 -> 0.25 (matches sqlglot behavior)
14806                if n.starts_with('.') {
14807                    self.write("0");
14808                    self.write(&n);
14809                } else if n.starts_with("-.") {
14810                    // Handle negative numbers like -.25 -> -0.25
14811                    self.write("-0");
14812                    self.write(&n[1..]);
14813                } else {
14814                    self.write(&n);
14815                }
14816            }
14817            Literal::HexString(h) => {
14818                // Most dialects use lowercase x'...' for hex literals.
14819                match self.config.dialect {
14820                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
14821                        self.write("0x");
14822                        self.write(h);
14823                        return Ok(());
14824                    }
14825                    Some(DialectType::Spark)
14826                    | Some(DialectType::Databricks)
14827                    | Some(DialectType::Teradata) => self.write("X'"),
14828                    _ => self.write("x'"),
14829                }
14830                self.write(h);
14831                self.write("'");
14832            }
14833            Literal::HexNumber(h) => {
14834                // Hex number (0xA) - integer in hex notation (from BigQuery)
14835                // For BigQuery, TSQL, Fabric output as 0xHEX (native hex notation)
14836                // For other dialects, convert to decimal integer
14837                match self.config.dialect {
14838                    Some(DialectType::BigQuery)
14839                    | Some(DialectType::ClickHouse)
14840                    | Some(DialectType::TSQL)
14841                    | Some(DialectType::Fabric) => {
14842                        self.write("0x");
14843                        self.write(h);
14844                    }
14845                    _ => {
14846                        // Convert hex to decimal
14847                        if let Ok(val) = u64::from_str_radix(h, 16) {
14848                            self.write(&val.to_string());
14849                        } else {
14850                            // Fallback: keep as 0x notation
14851                            self.write("0x");
14852                            self.write(h);
14853                        }
14854                    }
14855                }
14856            }
14857            Literal::BitString(b) => {
14858                // Bit string B'0101...'
14859                self.write("B'");
14860                self.write(b);
14861                self.write("'");
14862            }
14863            Literal::ByteString(b) => {
14864                // Byte string b'...' (BigQuery style)
14865                self.write("b'");
14866                // Escape special characters for output
14867                self.write_escaped_byte_string(b);
14868                self.write("'");
14869            }
14870            Literal::NationalString(s) => {
14871                // N'string' is supported by TSQL, Oracle, MySQL, and generic SQL
14872                // Other dialects strip the N prefix and output as regular string
14873                let keep_n_prefix = matches!(
14874                    self.config.dialect,
14875                    Some(DialectType::TSQL)
14876                        | Some(DialectType::Oracle)
14877                        | Some(DialectType::MySQL)
14878                        | None
14879                );
14880                if keep_n_prefix {
14881                    self.write("N'");
14882                } else {
14883                    self.write("'");
14884                }
14885                self.write(s);
14886                self.write("'");
14887            }
14888            Literal::Date(d) => {
14889                self.generate_date_literal(d)?;
14890            }
14891            Literal::Time(t) => {
14892                self.generate_time_literal(t)?;
14893            }
14894            Literal::Timestamp(ts) => {
14895                self.generate_timestamp_literal(ts)?;
14896            }
14897            Literal::Datetime(dt) => {
14898                self.generate_datetime_literal(dt)?;
14899            }
14900            Literal::TripleQuotedString(s, _quote_char) => {
14901                // For BigQuery and other dialects that don't support triple-quote, normalize to regular strings
14902                if matches!(
14903                    self.config.dialect,
14904                    Some(crate::dialects::DialectType::BigQuery)
14905                        | Some(crate::dialects::DialectType::DuckDB)
14906                        | Some(crate::dialects::DialectType::Snowflake)
14907                        | Some(crate::dialects::DialectType::Spark)
14908                        | Some(crate::dialects::DialectType::Hive)
14909                        | Some(crate::dialects::DialectType::Presto)
14910                        | Some(crate::dialects::DialectType::Trino)
14911                        | Some(crate::dialects::DialectType::PostgreSQL)
14912                        | Some(crate::dialects::DialectType::MySQL)
14913                        | Some(crate::dialects::DialectType::Redshift)
14914                        | Some(crate::dialects::DialectType::TSQL)
14915                        | Some(crate::dialects::DialectType::Oracle)
14916                        | Some(crate::dialects::DialectType::ClickHouse)
14917                        | Some(crate::dialects::DialectType::Databricks)
14918                        | Some(crate::dialects::DialectType::SQLite)
14919                ) {
14920                    self.generate_string_literal(s)?;
14921                } else {
14922                    // Preserve triple-quoted string syntax for generic/unknown dialects
14923                    let quotes = format!("{0}{0}{0}", _quote_char);
14924                    self.write(&quotes);
14925                    self.write(s);
14926                    self.write(&quotes);
14927                }
14928            }
14929            Literal::EscapeString(s) => {
14930                // PostgreSQL escape string: e'...' or E'...'
14931                // Token text format is "e:content" or "E:content"
14932                // Normalize escape sequences: \' -> '' (standard SQL doubled quote)
14933                use crate::dialects::DialectType;
14934                let content = if let Some(c) = s.strip_prefix("e:") {
14935                    c
14936                } else if let Some(c) = s.strip_prefix("E:") {
14937                    c
14938                } else {
14939                    s.as_str()
14940                };
14941
14942                // MySQL strips the PostgreSQL E prefix but still emits a string literal.
14943                if matches!(
14944                    self.config.dialect,
14945                    Some(DialectType::MySQL) | Some(DialectType::TiDB)
14946                ) {
14947                    self.write("'");
14948                    self.write(&content.replace('\'', "''"));
14949                    self.write("'");
14950                } else {
14951                    // Some dialects use lowercase e' prefix
14952                    let prefix = if matches!(
14953                        self.config.dialect,
14954                        Some(DialectType::SingleStore)
14955                            | Some(DialectType::DuckDB)
14956                            | Some(DialectType::PostgreSQL)
14957                            | Some(DialectType::CockroachDB)
14958                            | Some(DialectType::Materialize)
14959                            | Some(DialectType::RisingWave)
14960                    ) {
14961                        "e'"
14962                    } else {
14963                        "E'"
14964                    };
14965
14966                    // Normalize \' to '' for output
14967                    let normalized = content.replace("\\'", "''");
14968                    self.write(prefix);
14969                    self.write(&normalized);
14970                    self.write("'");
14971                }
14972            }
14973            Literal::DollarString(s) => {
14974                // Convert dollar-quoted strings to single-quoted strings
14975                // (like Python sqlglot's rawstring_sql)
14976                use crate::dialects::DialectType;
14977                // Extract content from tag\x00content format
14978                let (_tag, content) = crate::tokens::parse_dollar_string_token(s);
14979                // Step 1: Escape backslashes if the dialect uses backslash as a string escape
14980                let escape_backslash = matches!(
14981                    self.config.dialect,
14982                    Some(DialectType::ClickHouse) | Some(DialectType::Snowflake)
14983                );
14984                // Step 2: Determine quote escaping style
14985                // Snowflake: ' -> \' (backslash escape)
14986                // PostgreSQL, DuckDB, others: ' -> '' (doubled quote)
14987                let use_backslash_quote =
14988                    matches!(self.config.dialect, Some(DialectType::Snowflake));
14989
14990                let mut escaped = String::with_capacity(content.len() + 4);
14991                for ch in content.chars() {
14992                    if escape_backslash && ch == '\\' {
14993                        // Escape backslash first (before quote escaping)
14994                        escaped.push('\\');
14995                        escaped.push('\\');
14996                    } else if ch == '\'' {
14997                        if use_backslash_quote {
14998                            escaped.push('\\');
14999                            escaped.push('\'');
15000                        } else {
15001                            escaped.push('\'');
15002                            escaped.push('\'');
15003                        }
15004                    } else {
15005                        escaped.push(ch);
15006                    }
15007                }
15008                self.write("'");
15009                self.write(&escaped);
15010                self.write("'");
15011            }
15012            Literal::RawString(s) => {
15013                // Raw strings (r"..." or r'...') contain literal backslashes.
15014                // When converting to a regular string, this follows Python sqlglot's rawstring_sql:
15015                // 1. If \\ is in STRING_ESCAPES, double all backslashes
15016                // 2. Apply ESCAPED_SEQUENCES for special chars (but NOT for backslash itself)
15017                // 3. Escape quotes using STRING_ESCAPES[0] + quote_char
15018                use crate::dialects::DialectType;
15019
15020                // Dialects where \\ is in STRING_ESCAPES (backslashes need doubling)
15021                let escape_backslash = matches!(
15022                    self.config.dialect,
15023                    Some(DialectType::BigQuery)
15024                        | Some(DialectType::MySQL)
15025                        | Some(DialectType::SingleStore)
15026                        | Some(DialectType::TiDB)
15027                        | Some(DialectType::Hive)
15028                        | Some(DialectType::Spark)
15029                        | Some(DialectType::Databricks)
15030                        | Some(DialectType::Drill)
15031                        | Some(DialectType::Snowflake)
15032                        | Some(DialectType::Redshift)
15033                        | Some(DialectType::ClickHouse)
15034                );
15035
15036                // Dialects where backslash is the PRIMARY string escape (STRING_ESCAPES[0] = "\\")
15037                // These escape quotes as \' instead of ''
15038                let backslash_escapes_quote = matches!(
15039                    self.config.dialect,
15040                    Some(DialectType::BigQuery)
15041                        | Some(DialectType::Hive)
15042                        | Some(DialectType::Spark)
15043                        | Some(DialectType::Databricks)
15044                        | Some(DialectType::Drill)
15045                        | Some(DialectType::Snowflake)
15046                        | Some(DialectType::Redshift)
15047                );
15048
15049                // Whether this dialect supports escaped sequences (ESCAPED_SEQUENCES mapping)
15050                // This is True when \\ is in STRING_ESCAPES (same as escape_backslash)
15051                let supports_escape_sequences = escape_backslash;
15052
15053                let mut escaped = String::with_capacity(s.len() + 4);
15054                for ch in s.chars() {
15055                    if escape_backslash && ch == '\\' {
15056                        // Double the backslash for the target dialect
15057                        escaped.push('\\');
15058                        escaped.push('\\');
15059                    } else if ch == '\'' {
15060                        if backslash_escapes_quote {
15061                            // Use backslash to escape the quote: \'
15062                            escaped.push('\\');
15063                            escaped.push('\'');
15064                        } else {
15065                            // Use SQL standard quote doubling: ''
15066                            escaped.push('\'');
15067                            escaped.push('\'');
15068                        }
15069                    } else if supports_escape_sequences {
15070                        // Apply ESCAPED_SEQUENCES mapping for special chars
15071                        // (escape_backslash=False in rawstring_sql, so \\ is NOT escaped here)
15072                        match ch {
15073                            '\n' => {
15074                                escaped.push('\\');
15075                                escaped.push('n');
15076                            }
15077                            '\r' => {
15078                                escaped.push('\\');
15079                                escaped.push('r');
15080                            }
15081                            '\t' => {
15082                                escaped.push('\\');
15083                                escaped.push('t');
15084                            }
15085                            '\x07' => {
15086                                escaped.push('\\');
15087                                escaped.push('a');
15088                            }
15089                            '\x08' => {
15090                                escaped.push('\\');
15091                                escaped.push('b');
15092                            }
15093                            '\x0C' => {
15094                                escaped.push('\\');
15095                                escaped.push('f');
15096                            }
15097                            '\x0B' => {
15098                                escaped.push('\\');
15099                                escaped.push('v');
15100                            }
15101                            _ => escaped.push(ch),
15102                        }
15103                    } else {
15104                        escaped.push(ch);
15105                    }
15106                }
15107                self.write("'");
15108                self.write(&escaped);
15109                self.write("'");
15110            }
15111        }
15112        Ok(())
15113    }
15114
15115    /// Generate a DATE literal with dialect-specific formatting
15116    fn generate_date_literal(&mut self, d: &str) -> Result<()> {
15117        use crate::dialects::DialectType;
15118
15119        match self.config.dialect {
15120            // SQL Server / Fabric use CONVERT or CAST
15121            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
15122                self.write("CAST('");
15123                self.write(d);
15124                self.write("' AS DATE)");
15125            }
15126            // BigQuery uses CAST syntax for type literals
15127            // DATE 'value' -> CAST('value' AS DATE)
15128            Some(DialectType::BigQuery) => {
15129                self.write("CAST('");
15130                self.write(d);
15131                self.write("' AS DATE)");
15132            }
15133            // Exasol uses CAST syntax for DATE literals
15134            // DATE 'value' -> CAST('value' AS DATE)
15135            Some(DialectType::Exasol) => {
15136                self.write("CAST('");
15137                self.write(d);
15138                self.write("' AS DATE)");
15139            }
15140            // Snowflake uses CAST syntax for DATE literals
15141            // DATE 'value' -> CAST('value' AS DATE)
15142            Some(DialectType::Snowflake) => {
15143                self.write("CAST('");
15144                self.write(d);
15145                self.write("' AS DATE)");
15146            }
15147            // PostgreSQL, MySQL, Redshift: DATE 'value' -> CAST('value' AS DATE)
15148            Some(DialectType::PostgreSQL)
15149            | Some(DialectType::MySQL)
15150            | Some(DialectType::SingleStore)
15151            | Some(DialectType::TiDB)
15152            | Some(DialectType::Redshift) => {
15153                self.write("CAST('");
15154                self.write(d);
15155                self.write("' AS DATE)");
15156            }
15157            // DuckDB, Presto, Trino, Spark: DATE 'value' -> CAST('value' AS DATE)
15158            Some(DialectType::DuckDB)
15159            | Some(DialectType::Presto)
15160            | Some(DialectType::Trino)
15161            | Some(DialectType::Athena)
15162            | Some(DialectType::Spark)
15163            | Some(DialectType::Databricks)
15164            | Some(DialectType::Hive) => {
15165                self.write("CAST('");
15166                self.write(d);
15167                self.write("' AS DATE)");
15168            }
15169            // Oracle: DATE 'value' -> TO_DATE('value', 'YYYY-MM-DD')
15170            Some(DialectType::Oracle) => {
15171                self.write("TO_DATE('");
15172                self.write(d);
15173                self.write("', 'YYYY-MM-DD')");
15174            }
15175            // Standard SQL: DATE '...'
15176            _ => {
15177                self.write_keyword("DATE");
15178                self.write(" '");
15179                self.write(d);
15180                self.write("'");
15181            }
15182        }
15183        Ok(())
15184    }
15185
15186    /// Generate a TIME literal with dialect-specific formatting
15187    fn generate_time_literal(&mut self, t: &str) -> Result<()> {
15188        use crate::dialects::DialectType;
15189
15190        match self.config.dialect {
15191            // SQL Server uses CONVERT or CAST
15192            Some(DialectType::TSQL) => {
15193                self.write("CAST('");
15194                self.write(t);
15195                self.write("' AS TIME)");
15196            }
15197            // Standard SQL: TIME '...'
15198            _ => {
15199                self.write_keyword("TIME");
15200                self.write(" '");
15201                self.write(t);
15202                self.write("'");
15203            }
15204        }
15205        Ok(())
15206    }
15207
15208    /// Generate a date expression for Dremio, converting DATE literals to CAST
15209    fn generate_dremio_date_expression(&mut self, expr: &Expression) -> Result<()> {
15210        use crate::expressions::Literal;
15211
15212        match expr {
15213            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
15214                let Literal::Date(d) = lit.as_ref() else {
15215                    unreachable!()
15216                };
15217                // DATE 'value' -> CAST('value' AS DATE)
15218                self.write("CAST('");
15219                self.write(d);
15220                self.write("' AS DATE)");
15221            }
15222            _ => {
15223                // For all other expressions, generate normally
15224                self.generate_expression(expr)?;
15225            }
15226        }
15227        Ok(())
15228    }
15229
15230    /// Generate a TIMESTAMP literal with dialect-specific formatting
15231    fn generate_timestamp_literal(&mut self, ts: &str) -> Result<()> {
15232        use crate::dialects::DialectType;
15233
15234        match self.config.dialect {
15235            // SQL Server uses CONVERT or CAST
15236            Some(DialectType::TSQL) => {
15237                self.write("CAST('");
15238                self.write(ts);
15239                self.write("' AS DATETIME2)");
15240            }
15241            // BigQuery uses CAST syntax for type literals
15242            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15243            Some(DialectType::BigQuery) => {
15244                self.write("CAST('");
15245                self.write(ts);
15246                self.write("' AS TIMESTAMP)");
15247            }
15248            // Snowflake uses CAST syntax for TIMESTAMP literals
15249            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15250            Some(DialectType::Snowflake) => {
15251                self.write("CAST('");
15252                self.write(ts);
15253                self.write("' AS TIMESTAMP)");
15254            }
15255            // Dremio uses CAST syntax for TIMESTAMP literals
15256            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15257            Some(DialectType::Dremio) => {
15258                self.write("CAST('");
15259                self.write(ts);
15260                self.write("' AS TIMESTAMP)");
15261            }
15262            // Exasol uses CAST syntax for TIMESTAMP literals
15263            // TIMESTAMP 'value' -> CAST('value' AS TIMESTAMP)
15264            Some(DialectType::Exasol) => {
15265                self.write("CAST('");
15266                self.write(ts);
15267                self.write("' AS TIMESTAMP)");
15268            }
15269            // Oracle prefers TO_TIMESTAMP function call
15270            // TIMESTAMP 'value' -> TO_TIMESTAMP('value', 'YYYY-MM-DD HH24:MI:SS.FF6')
15271            Some(DialectType::Oracle) => {
15272                self.write("TO_TIMESTAMP('");
15273                self.write(ts);
15274                self.write("', 'YYYY-MM-DD HH24:MI:SS.FF6')");
15275            }
15276            // Presto/Trino: always use CAST for TIMESTAMP literals
15277            Some(DialectType::Presto) | Some(DialectType::Trino) => {
15278                if Self::timestamp_has_timezone(ts) {
15279                    self.write("CAST('");
15280                    self.write(ts);
15281                    self.write("' AS TIMESTAMP WITH TIME ZONE)");
15282                } else {
15283                    self.write("CAST('");
15284                    self.write(ts);
15285                    self.write("' AS TIMESTAMP)");
15286                }
15287            }
15288            // ClickHouse: CAST('...' AS Nullable(DateTime))
15289            Some(DialectType::ClickHouse) => {
15290                self.write("CAST('");
15291                self.write(ts);
15292                self.write("' AS Nullable(DateTime))");
15293            }
15294            // Spark: CAST('...' AS TIMESTAMP)
15295            Some(DialectType::Spark) => {
15296                self.write("CAST('");
15297                self.write(ts);
15298                self.write("' AS TIMESTAMP)");
15299            }
15300            // Redshift: CAST('...' AS TIMESTAMP) for regular timestamps,
15301            // but TIMESTAMP '...' for special values like 'epoch'
15302            Some(DialectType::Redshift) => {
15303                if ts == "epoch" {
15304                    self.write_keyword("TIMESTAMP");
15305                    self.write(" '");
15306                    self.write(ts);
15307                    self.write("'");
15308                } else {
15309                    self.write("CAST('");
15310                    self.write(ts);
15311                    self.write("' AS TIMESTAMP)");
15312                }
15313            }
15314            // PostgreSQL, Hive, DuckDB, etc.: CAST('...' AS TIMESTAMP)
15315            Some(DialectType::PostgreSQL)
15316            | Some(DialectType::Hive)
15317            | Some(DialectType::SQLite)
15318            | Some(DialectType::DuckDB)
15319            | Some(DialectType::Athena)
15320            | Some(DialectType::Drill)
15321            | Some(DialectType::Teradata) => {
15322                self.write("CAST('");
15323                self.write(ts);
15324                self.write("' AS TIMESTAMP)");
15325            }
15326            // MySQL/StarRocks: CAST('...' AS DATETIME)
15327            Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
15328                self.write("CAST('");
15329                self.write(ts);
15330                self.write("' AS DATETIME)");
15331            }
15332            // Databricks: CAST('...' AS TIMESTAMP_NTZ)
15333            Some(DialectType::Databricks) => {
15334                self.write("CAST('");
15335                self.write(ts);
15336                self.write("' AS TIMESTAMP_NTZ)");
15337            }
15338            // Standard SQL: TIMESTAMP '...'
15339            _ => {
15340                self.write_keyword("TIMESTAMP");
15341                self.write(" '");
15342                self.write(ts);
15343                self.write("'");
15344            }
15345        }
15346        Ok(())
15347    }
15348
15349    /// Check if a timestamp string contains a timezone identifier
15350    /// This detects IANA timezone names like Europe/Prague, America/New_York, etc.
15351    fn timestamp_has_timezone(ts: &str) -> bool {
15352        // Check for common IANA timezone patterns: Continent/City format
15353        // Examples: Europe/Prague, America/New_York, Asia/Tokyo, etc.
15354        // Also handles: UTC, GMT, Etc/GMT+0, etc.
15355        let ts_lower = ts.to_ascii_lowercase();
15356
15357        // Check for Continent/City pattern (most common)
15358        let continent_prefixes = [
15359            "africa/",
15360            "america/",
15361            "antarctica/",
15362            "arctic/",
15363            "asia/",
15364            "atlantic/",
15365            "australia/",
15366            "europe/",
15367            "indian/",
15368            "pacific/",
15369            "etc/",
15370            "brazil/",
15371            "canada/",
15372            "chile/",
15373            "mexico/",
15374            "us/",
15375        ];
15376
15377        for prefix in &continent_prefixes {
15378            if ts_lower.contains(prefix) {
15379                return true;
15380            }
15381        }
15382
15383        // Check for standalone timezone abbreviations at the end
15384        // These typically appear after the time portion
15385        let tz_abbrevs = [
15386            " utc", " gmt", " cet", " cest", " eet", " eest", " wet", " west", " est", " edt",
15387            " cst", " cdt", " mst", " mdt", " pst", " pdt", " ist", " bst", " jst", " kst", " hkt",
15388            " sgt", " aest", " aedt", " acst", " acdt", " awst",
15389        ];
15390
15391        for abbrev in &tz_abbrevs {
15392            if ts_lower.ends_with(abbrev) {
15393                return true;
15394            }
15395        }
15396
15397        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN
15398        // Examples: "2012-10-31 01:00 -2", "2012-10-31 01:00 +02:00"
15399        // Look for pattern: space followed by + or - and digits (optionally with :)
15400        let trimmed = ts.trim();
15401        if let Some(last_space) = trimmed.rfind(' ') {
15402            let suffix = &trimmed[last_space + 1..];
15403            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
15404                // Check if rest is numeric (possibly with : for hh:mm format)
15405                let rest = &suffix[1..];
15406                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
15407                    return true;
15408                }
15409            }
15410        }
15411
15412        false
15413    }
15414
15415    /// Generate a DATETIME literal with dialect-specific formatting
15416    fn generate_datetime_literal(&mut self, dt: &str) -> Result<()> {
15417        use crate::dialects::DialectType;
15418
15419        match self.config.dialect {
15420            // BigQuery uses CAST syntax for type literals
15421            // DATETIME 'value' -> CAST('value' AS DATETIME)
15422            Some(DialectType::BigQuery) => {
15423                self.write("CAST('");
15424                self.write(dt);
15425                self.write("' AS DATETIME)");
15426            }
15427            // DuckDB: DATETIME -> CAST('value' AS TIMESTAMP)
15428            Some(DialectType::DuckDB) => {
15429                self.write("CAST('");
15430                self.write(dt);
15431                self.write("' AS TIMESTAMP)");
15432            }
15433            // DATETIME is primarily a BigQuery type
15434            // Output as DATETIME '...' for dialects that support it
15435            _ => {
15436                self.write_keyword("DATETIME");
15437                self.write(" '");
15438                self.write(dt);
15439                self.write("'");
15440            }
15441        }
15442        Ok(())
15443    }
15444
15445    /// Generate a string literal with dialect-specific escaping
15446    fn generate_string_literal(&mut self, s: &str) -> Result<()> {
15447        use crate::dialects::DialectType;
15448
15449        match self.config.dialect {
15450            // MySQL/Hive: Uses SQL standard quote escaping ('') for quotes,
15451            // and backslash escaping for special characters like newlines
15452            // Hive STRING_ESCAPES = ["\\"] - uses backslash escapes
15453            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
15454                // Hive/Spark use backslash escaping for quotes (\') and special chars
15455                self.write("'");
15456                for c in s.chars() {
15457                    match c {
15458                        '\'' => self.write("\\'"),
15459                        '\\' => self.write("\\\\"),
15460                        '\n' => self.write("\\n"),
15461                        '\r' => self.write("\\r"),
15462                        '\t' => self.write("\\t"),
15463                        '\0' => self.write("\\0"),
15464                        _ => self.output.push(c),
15465                    }
15466                }
15467                self.write("'");
15468            }
15469            Some(DialectType::Drill) => {
15470                // Drill uses SQL-standard quote doubling ('') for quotes,
15471                // but backslash escaping for special characters
15472                self.write("'");
15473                for c in s.chars() {
15474                    match c {
15475                        '\'' => self.write("''"),
15476                        '\\' => self.write("\\\\"),
15477                        '\n' => self.write("\\n"),
15478                        '\r' => self.write("\\r"),
15479                        '\t' => self.write("\\t"),
15480                        '\0' => self.write("\\0"),
15481                        _ => self.output.push(c),
15482                    }
15483                }
15484                self.write("'");
15485            }
15486            Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB) => {
15487                self.write("'");
15488                for c in s.chars() {
15489                    match c {
15490                        // MySQL uses SQL standard quote doubling
15491                        '\'' => self.write("''"),
15492                        '\\' => self.write("\\\\"),
15493                        '\n' => self.write("\\n"),
15494                        '\r' => self.write("\\r"),
15495                        '\t' => self.write("\\t"),
15496                        // sqlglot writes a literal NUL for this case
15497                        '\0' => self.output.push('\0'),
15498                        _ => self.output.push(c),
15499                    }
15500                }
15501                self.write("'");
15502            }
15503            // BigQuery: Uses backslash escaping
15504            Some(DialectType::BigQuery) => {
15505                self.write("'");
15506                for c in s.chars() {
15507                    match c {
15508                        '\'' => self.write("\\'"),
15509                        '\\' => self.write("\\\\"),
15510                        '\n' => self.write("\\n"),
15511                        '\r' => self.write("\\r"),
15512                        '\t' => self.write("\\t"),
15513                        '\0' => self.write("\\0"),
15514                        '\x07' => self.write("\\a"),
15515                        '\x08' => self.write("\\b"),
15516                        '\x0C' => self.write("\\f"),
15517                        '\x0B' => self.write("\\v"),
15518                        _ => self.output.push(c),
15519                    }
15520                }
15521                self.write("'");
15522            }
15523            // Athena: Uses different escaping for DDL (Hive) vs DML (Trino)
15524            // In Hive context (DDL): backslash escaping for single quotes (\') and backslashes (\\)
15525            // In Trino context (DML): SQL-standard escaping ('') and literal backslashes
15526            Some(DialectType::Athena) => {
15527                if self.athena_hive_context {
15528                    // Hive-style: backslash escaping
15529                    self.write("'");
15530                    for c in s.chars() {
15531                        match c {
15532                            '\'' => self.write("\\'"),
15533                            '\\' => self.write("\\\\"),
15534                            '\n' => self.write("\\n"),
15535                            '\r' => self.write("\\r"),
15536                            '\t' => self.write("\\t"),
15537                            '\0' => self.write("\\0"),
15538                            _ => self.output.push(c),
15539                        }
15540                    }
15541                    self.write("'");
15542                } else {
15543                    // Trino-style: SQL-standard escaping, preserve backslashes
15544                    self.write("'");
15545                    for c in s.chars() {
15546                        match c {
15547                            '\'' => self.write("''"),
15548                            // Preserve backslashes literally (no re-escaping)
15549                            _ => self.output.push(c),
15550                        }
15551                    }
15552                    self.write("'");
15553                }
15554            }
15555            // Snowflake: Uses backslash escaping (STRING_ESCAPES = ["\\", "'"])
15556            // The tokenizer preserves backslash escape sequences literally (e.g., input '\\'
15557            // becomes string value '\\'), so we should NOT re-escape backslashes.
15558            // We only need to escape single quotes.
15559            Some(DialectType::Snowflake) => {
15560                self.write("'");
15561                for c in s.chars() {
15562                    match c {
15563                        '\'' => self.write("\\'"),
15564                        // Backslashes are already escaped in the tokenized string, don't re-escape
15565                        // Only escape special characters that might not have been escaped
15566                        '\n' => self.write("\\n"),
15567                        '\r' => self.write("\\r"),
15568                        '\t' => self.write("\\t"),
15569                        _ => self.output.push(c),
15570                    }
15571                }
15572                self.write("'");
15573            }
15574            // PostgreSQL: Output special characters as literal chars in strings (no E-string prefix)
15575            Some(DialectType::PostgreSQL) => {
15576                self.write("'");
15577                for c in s.chars() {
15578                    match c {
15579                        '\'' => self.write("''"),
15580                        _ => self.output.push(c),
15581                    }
15582                }
15583                self.write("'");
15584            }
15585            // Redshift: Uses backslash escaping for single quotes
15586            Some(DialectType::Redshift) => {
15587                self.write("'");
15588                for c in s.chars() {
15589                    match c {
15590                        '\'' => self.write("\\'"),
15591                        _ => self.output.push(c),
15592                    }
15593                }
15594                self.write("'");
15595            }
15596            // Oracle: Uses standard double single-quote escaping
15597            Some(DialectType::Oracle) => {
15598                self.write("'");
15599                for ch in s.chars() {
15600                    if ch == '\'' {
15601                        self.output.push_str("''");
15602                    } else {
15603                        self.output.push(ch);
15604                    }
15605                }
15606                self.write("'");
15607            }
15608            // ClickHouse: Uses SQL-standard quote doubling ('') for quotes,
15609            // backslash escaping for backslashes and special characters
15610            Some(DialectType::ClickHouse) => {
15611                self.write("'");
15612                for c in s.chars() {
15613                    match c {
15614                        '\'' => self.write("''"),
15615                        '\\' => self.write("\\\\"),
15616                        '\n' => self.write("\\n"),
15617                        '\r' => self.write("\\r"),
15618                        '\t' => self.write("\\t"),
15619                        '\0' => self.write("\\0"),
15620                        '\x07' => self.write("\\a"),
15621                        '\x08' => self.write("\\b"),
15622                        '\x0C' => self.write("\\f"),
15623                        '\x0B' => self.write("\\v"),
15624                        // Non-printable characters: emit as \xNN hex escapes
15625                        c if c.is_control() || (c as u32) < 0x20 => {
15626                            let byte = c as u32;
15627                            if byte < 256 {
15628                                self.write(&format!("\\x{:02X}", byte));
15629                            } else {
15630                                self.output.push(c);
15631                            }
15632                        }
15633                        _ => self.output.push(c),
15634                    }
15635                }
15636                self.write("'");
15637            }
15638            // Default: SQL standard double single quotes (works for most dialects)
15639            // PostgreSQL, Snowflake, DuckDB, TSQL, etc.
15640            _ => {
15641                self.write("'");
15642                for ch in s.chars() {
15643                    if ch == '\'' {
15644                        self.output.push_str("''");
15645                    } else {
15646                        self.output.push(ch);
15647                    }
15648                }
15649                self.write("'");
15650            }
15651        }
15652        Ok(())
15653    }
15654
15655    /// Write a byte string with proper escaping for BigQuery-style byte literals
15656    /// Escapes characters as \xNN hex escapes where needed
15657    fn write_escaped_byte_string(&mut self, s: &str) {
15658        for c in s.chars() {
15659            match c {
15660                // Escape single quotes
15661                '\'' => self.write("\\'"),
15662                // Escape backslashes
15663                '\\' => self.write("\\\\"),
15664                // Keep all printable characters (including non-ASCII) as-is
15665                _ if !c.is_control() => self.output.push(c),
15666                // Escape control characters as hex
15667                _ => {
15668                    let byte = c as u32;
15669                    if byte < 256 {
15670                        self.write(&format!("\\x{:02x}", byte));
15671                    } else {
15672                        // For unicode characters, write each UTF-8 byte
15673                        for b in c.to_string().as_bytes() {
15674                            self.write(&format!("\\x{:02x}", b));
15675                        }
15676                    }
15677                }
15678            }
15679        }
15680    }
15681
15682    fn generate_boolean(&mut self, b: &BooleanLiteral) -> Result<()> {
15683        use crate::dialects::DialectType;
15684
15685        // Different dialects have different boolean literal formats
15686        match self.config.dialect {
15687            // SQL Server typically uses 1/0 for boolean literals in many contexts
15688            // However, TRUE/FALSE also works in modern versions
15689            Some(DialectType::TSQL) => {
15690                self.write(if b.value { "1" } else { "0" });
15691            }
15692            // Oracle traditionally uses 1/0 (no native boolean until recent versions)
15693            Some(DialectType::Oracle) => {
15694                self.write(if b.value { "1" } else { "0" });
15695            }
15696            // MySQL accepts TRUE/FALSE as aliases for 1/0
15697            Some(DialectType::MySQL) => {
15698                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15699            }
15700            // Most other dialects support TRUE/FALSE
15701            _ => {
15702                self.write_keyword(if b.value { "TRUE" } else { "FALSE" });
15703            }
15704        }
15705        Ok(())
15706    }
15707
15708    /// Generate an identifier that's used as an alias name
15709    /// This quotes reserved keywords in addition to already-quoted identifiers
15710    fn generate_alias_identifier(&mut self, id: &Identifier) -> Result<()> {
15711        let name = &id.name;
15712        let quote_style = &self.config.identifier_quote_style;
15713
15714        // For aliases, quote if:
15715        // 1. The identifier was explicitly quoted in the source
15716        // 2. The identifier is a reserved keyword for the current dialect
15717        let needs_quoting = id.quoted || self.is_reserved_keyword(name);
15718
15719        // Normalize identifier if configured
15720        let output_name = if self.config.normalize_identifiers && !id.quoted {
15721            name.to_ascii_lowercase()
15722        } else {
15723            name.to_string()
15724        };
15725
15726        if needs_quoting {
15727            let quote_style = if matches!(self.config.dialect, Some(DialectType::ClickHouse))
15728                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
15729                && quote_style.start == '"'
15730                && output_name.contains('"')
15731            {
15732                &IdentifierQuoteStyle::BACKTICK
15733            } else {
15734                quote_style
15735            };
15736            // Escape any quote characters within the identifier
15737            let escaped_name = if quote_style.start == quote_style.end {
15738                output_name.replace(
15739                    quote_style.end,
15740                    &format!("{}{}", quote_style.end, quote_style.end),
15741                )
15742            } else {
15743                output_name.replace(
15744                    quote_style.end,
15745                    &format!("{}{}", quote_style.end, quote_style.end),
15746                )
15747            };
15748            self.write(&format!(
15749                "{}{}{}",
15750                quote_style.start, escaped_name, quote_style.end
15751            ));
15752        } else {
15753            self.write(&output_name);
15754        }
15755
15756        // Output trailing comments
15757        for comment in &id.trailing_comments {
15758            self.write(" ");
15759            self.write_formatted_comment(comment);
15760        }
15761        Ok(())
15762    }
15763
15764    fn generate_identifier(&mut self, id: &Identifier) -> Result<()> {
15765        use crate::dialects::DialectType;
15766
15767        let name = &id.name;
15768
15769        // For Athena, use backticks in Hive context, double quotes in Trino context
15770        let quote_style = if matches!(self.config.dialect, Some(DialectType::Athena))
15771            && self.athena_hive_context
15772        {
15773            &IdentifierQuoteStyle::BACKTICK
15774        } else {
15775            &self.config.identifier_quote_style
15776        };
15777
15778        // Quote if:
15779        // 1. The identifier was explicitly quoted in the source
15780        // 2. The identifier is a reserved keyword for the current dialect
15781        // 3. The config says to always quote identifiers (e.g., Athena/Presto)
15782        // This matches Python sqlglot's identifier_sql behavior
15783        // Also quote identifiers starting with digits if the target dialect doesn't support them
15784        let starts_with_digit = name.chars().next().map_or(false, |c| c.is_ascii_digit());
15785        let needs_digit_quoting = starts_with_digit
15786            && !self.config.identifiers_can_start_with_digit
15787            && self.config.dialect.is_some();
15788        let mysql_invalid_hex_identifier = matches!(self.config.dialect, Some(DialectType::MySQL))
15789            && name.len() > 2
15790            && (name.starts_with("0x") || name.starts_with("0X"))
15791            && !name[2..].chars().all(|c| c.is_ascii_hexdigit());
15792        let clickhouse_unsafe_identifier =
15793            matches!(self.config.dialect, Some(DialectType::ClickHouse))
15794                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
15795                && !name.starts_with('{')
15796                && !name.contains('(')
15797                && !name.contains(')')
15798                && name != "?"
15799                && name
15800                    .chars()
15801                    .any(|c| !(c.is_ascii_alphanumeric() || c == '_'));
15802        let needs_quoting = id.quoted
15803            || self.is_reserved_keyword(name)
15804            || self.config.always_quote_identifiers
15805            || needs_digit_quoting
15806            || mysql_invalid_hex_identifier
15807            || clickhouse_unsafe_identifier;
15808
15809        // Check for MySQL index column prefix length: name(16) or name(16) ASC/DESC
15810        // When quoted, we need to output `name`(16) not `name(16)`
15811        let (base_name, suffix) = if needs_quoting {
15812            // Try to extract prefix length from identifier: name(number) or name(number) ASC/DESC
15813            if let Some(paren_pos) = name.find('(') {
15814                let base = &name[..paren_pos];
15815                let rest = &name[paren_pos..];
15816                // Verify it looks like (digits) or (digits) ASC/DESC
15817                if rest.starts_with('(')
15818                    && (rest.ends_with(')') || rest.ends_with(") ASC") || rest.ends_with(") DESC"))
15819                {
15820                    // Check if content between parens is all digits
15821                    let close_paren = rest.find(')').unwrap_or(rest.len());
15822                    let inside = &rest[1..close_paren];
15823                    if inside.chars().all(|c| c.is_ascii_digit()) {
15824                        (base.to_string(), rest.to_string())
15825                    } else {
15826                        (name.to_string(), String::new())
15827                    }
15828                } else {
15829                    (name.to_string(), String::new())
15830                }
15831            } else if name.ends_with(" ASC") {
15832                let base = &name[..name.len() - 4];
15833                (base.to_string(), " ASC".to_string())
15834            } else if name.ends_with(" DESC") {
15835                let base = &name[..name.len() - 5];
15836                (base.to_string(), " DESC".to_string())
15837            } else {
15838                (name.to_string(), String::new())
15839            }
15840        } else {
15841            (name.to_string(), String::new())
15842        };
15843
15844        // Normalize identifier if configured, with special handling for Exasol
15845        // Exasol uses UPPERCASE normalization strategy, so reserved keywords that need quoting
15846        // should be uppercased when not already quoted (to match Python sqlglot behavior)
15847        let output_name = if self.config.normalize_identifiers && !id.quoted {
15848            base_name.to_ascii_lowercase()
15849        } else if matches!(self.config.dialect, Some(DialectType::Exasol))
15850            && !id.quoted
15851            && self.is_reserved_keyword(name)
15852        {
15853            // Exasol: uppercase reserved keywords when quoting them
15854            // This matches Python sqlglot's behavior with NORMALIZATION_STRATEGY = UPPERCASE
15855            base_name.to_ascii_uppercase()
15856        } else {
15857            base_name
15858        };
15859
15860        if needs_quoting {
15861            // Escape any quote characters within the identifier
15862            let escaped_name = if quote_style.start == quote_style.end {
15863                // Same start/end char (e.g., " or `) - double the quote char
15864                output_name.replace(
15865                    quote_style.end,
15866                    &format!("{}{}", quote_style.end, quote_style.end),
15867                )
15868            } else {
15869                // Different start/end (e.g., [ and ]) - escape only the end char
15870                output_name.replace(
15871                    quote_style.end,
15872                    &format!("{}{}", quote_style.end, quote_style.end),
15873                )
15874            };
15875            self.write(&format!(
15876                "{}{}{}{}",
15877                quote_style.start, escaped_name, quote_style.end, suffix
15878            ));
15879        } else {
15880            self.write(&output_name);
15881        }
15882
15883        // Output trailing comments
15884        for comment in &id.trailing_comments {
15885            self.write(" ");
15886            self.write_formatted_comment(comment);
15887        }
15888        Ok(())
15889    }
15890
15891    fn generate_column(&mut self, col: &Column) -> Result<()> {
15892        use crate::dialects::DialectType;
15893
15894        if let Some(table) = &col.table {
15895            // Exasol special case: LOCAL as column table prefix should NOT be quoted
15896            // LOCAL is a special keyword in Exasol for referencing aliases from the current scope
15897            // Only applies when: dialect is Exasol, name is "LOCAL" (case-insensitive), and not already quoted
15898            let is_exasol_local_prefix = matches!(self.config.dialect, Some(DialectType::Exasol))
15899                && !table.quoted
15900                && table.name.eq_ignore_ascii_case("LOCAL");
15901
15902            if is_exasol_local_prefix {
15903                // Write LOCAL unquoted (this is special Exasol syntax, not a table reference)
15904                self.write("LOCAL");
15905            } else {
15906                self.generate_identifier(table)?;
15907            }
15908            self.write(".");
15909        }
15910        self.generate_identifier(&col.name)?;
15911        // Oracle-style join marker (+)
15912        // Only output if dialect supports it (Oracle, Exasol)
15913        if col.join_mark && self.config.supports_column_join_marks {
15914            self.write(" (+)");
15915        }
15916        // Output trailing comments
15917        for comment in &col.trailing_comments {
15918            self.write_space();
15919            self.write_formatted_comment(comment);
15920        }
15921        Ok(())
15922    }
15923
15924    fn generate_prepare(&mut self, prepare: &PrepareStatement) -> Result<()> {
15925        self.write_keyword("PREPARE");
15926        self.write_space();
15927        self.generate_identifier(&prepare.name)?;
15928
15929        if !prepare.parameter_types.is_empty() {
15930            self.write(" (");
15931            for (i, data_type) in prepare.parameter_types.iter().enumerate() {
15932                if i > 0 {
15933                    self.write(", ");
15934                }
15935                self.generate_data_type(data_type)?;
15936            }
15937            self.write(")");
15938        }
15939
15940        self.write_space();
15941        self.write_keyword("AS");
15942        self.write_space();
15943        self.generate_expression(&prepare.statement)
15944    }
15945
15946    /// Generate a pseudocolumn (Oracle ROWNUM, ROWID, LEVEL, etc.)
15947    /// Pseudocolumns should NEVER be quoted, as quoting breaks them in Oracle
15948    fn generate_pseudocolumn(&mut self, pc: &Pseudocolumn) -> Result<()> {
15949        use crate::dialects::DialectType;
15950        use crate::expressions::PseudocolumnType;
15951
15952        // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift dialects
15953        if pc.kind == PseudocolumnType::Sysdate
15954            && !matches!(
15955                self.config.dialect,
15956                Some(DialectType::Oracle) | Some(DialectType::Redshift) | None
15957            )
15958        {
15959            self.write_keyword("CURRENT_TIMESTAMP");
15960            // Add () for dialects that expect it
15961            if matches!(
15962                self.config.dialect,
15963                Some(DialectType::MySQL)
15964                    | Some(DialectType::ClickHouse)
15965                    | Some(DialectType::Spark)
15966                    | Some(DialectType::Databricks)
15967                    | Some(DialectType::Hive)
15968            ) {
15969                self.write("()");
15970            }
15971        } else {
15972            self.write(pc.kind.as_str());
15973        }
15974        Ok(())
15975    }
15976
15977    /// Generate CONNECT BY clause (Oracle hierarchical queries)
15978    fn generate_connect(&mut self, connect: &Connect) -> Result<()> {
15979        use crate::dialects::DialectType;
15980
15981        // Generate native CONNECT BY for Oracle and Snowflake
15982        // For other dialects, add a comment noting manual conversion needed
15983        let supports_connect_by = matches!(
15984            self.config.dialect,
15985            Some(DialectType::Oracle) | Some(DialectType::Snowflake)
15986        );
15987
15988        if !supports_connect_by && self.config.dialect.is_some() {
15989            // Add comment for unsupported dialects
15990            if self.config.pretty {
15991                self.write_newline();
15992            } else {
15993                self.write_space();
15994            }
15995            self.write_unsupported_comment(
15996                "CONNECT BY requires manual conversion to recursive CTE",
15997            )?;
15998        }
15999
16000        // Generate START WITH if present (before CONNECT BY)
16001        if let Some(start) = &connect.start {
16002            if self.config.pretty {
16003                self.write_newline();
16004            } else {
16005                self.write_space();
16006            }
16007            self.write_keyword("START WITH");
16008            self.write_space();
16009            self.generate_expression(start)?;
16010        }
16011
16012        // Generate CONNECT BY
16013        if self.config.pretty {
16014            self.write_newline();
16015        } else {
16016            self.write_space();
16017        }
16018        self.write_keyword("CONNECT BY");
16019        if connect.nocycle {
16020            self.write_space();
16021            self.write_keyword("NOCYCLE");
16022        }
16023        self.write_space();
16024        self.generate_expression(&connect.connect)?;
16025
16026        Ok(())
16027    }
16028
16029    /// Generate Connect expression (for Expression::Connect variant)
16030    fn generate_connect_expr(&mut self, connect: &Connect) -> Result<()> {
16031        self.generate_connect(connect)
16032    }
16033
16034    /// Generate PRIOR expression
16035    fn generate_prior(&mut self, prior: &Prior) -> Result<()> {
16036        self.write_keyword("PRIOR");
16037        self.write_space();
16038        self.generate_expression(&prior.this)?;
16039        Ok(())
16040    }
16041
16042    /// Generate CONNECT_BY_ROOT function
16043    /// Syntax: CONNECT_BY_ROOT column (no parentheses)
16044    fn generate_connect_by_root(&mut self, cbr: &ConnectByRoot) -> Result<()> {
16045        self.write_keyword("CONNECT_BY_ROOT");
16046        self.write_space();
16047        self.generate_expression(&cbr.this)?;
16048        Ok(())
16049    }
16050
16051    /// Generate MATCH_RECOGNIZE clause
16052    fn generate_match_recognize(&mut self, mr: &MatchRecognize) -> Result<()> {
16053        use crate::dialects::DialectType;
16054
16055        // MATCH_RECOGNIZE is supported in Oracle, Snowflake, Presto, and Trino
16056        let supports_match_recognize = matches!(
16057            self.config.dialect,
16058            Some(DialectType::Oracle)
16059                | Some(DialectType::Snowflake)
16060                | Some(DialectType::Presto)
16061                | Some(DialectType::Trino)
16062        );
16063
16064        // Generate the source table first
16065        if let Some(source) = &mr.this {
16066            self.generate_expression(source)?;
16067        }
16068
16069        if !supports_match_recognize {
16070            self.write_unsupported_comment("MATCH_RECOGNIZE not supported in this dialect")?;
16071            return Ok(());
16072        }
16073
16074        // In pretty mode, MATCH_RECOGNIZE should be on a new line
16075        if self.config.pretty {
16076            self.write_newline();
16077        } else {
16078            self.write_space();
16079        }
16080
16081        self.write_keyword("MATCH_RECOGNIZE");
16082        self.write(" (");
16083
16084        if self.config.pretty {
16085            self.indent_level += 1;
16086        }
16087
16088        let mut needs_separator = false;
16089
16090        // PARTITION BY
16091        if let Some(partition_by) = &mr.partition_by {
16092            if !partition_by.is_empty() {
16093                if self.config.pretty {
16094                    self.write_newline();
16095                    self.write_indent();
16096                }
16097                self.write_keyword("PARTITION BY");
16098                self.write_space();
16099                for (i, expr) in partition_by.iter().enumerate() {
16100                    if i > 0 {
16101                        self.write(", ");
16102                    }
16103                    self.generate_expression(expr)?;
16104                }
16105                needs_separator = true;
16106            }
16107        }
16108
16109        // ORDER BY
16110        if let Some(order_by) = &mr.order_by {
16111            if !order_by.is_empty() {
16112                if needs_separator {
16113                    if self.config.pretty {
16114                        self.write_newline();
16115                        self.write_indent();
16116                    } else {
16117                        self.write_space();
16118                    }
16119                } else if self.config.pretty {
16120                    self.write_newline();
16121                    self.write_indent();
16122                }
16123                self.write_keyword("ORDER BY");
16124                // In pretty mode, put each ORDER BY column on a new indented line
16125                if self.config.pretty {
16126                    self.indent_level += 1;
16127                    for (i, ordered) in order_by.iter().enumerate() {
16128                        if i > 0 {
16129                            self.write(",");
16130                        }
16131                        self.write_newline();
16132                        self.write_indent();
16133                        self.generate_ordered(ordered)?;
16134                    }
16135                    self.indent_level -= 1;
16136                } else {
16137                    self.write_space();
16138                    for (i, ordered) in order_by.iter().enumerate() {
16139                        if i > 0 {
16140                            self.write(", ");
16141                        }
16142                        self.generate_ordered(ordered)?;
16143                    }
16144                }
16145                needs_separator = true;
16146            }
16147        }
16148
16149        // MEASURES
16150        if let Some(measures) = &mr.measures {
16151            if !measures.is_empty() {
16152                if needs_separator {
16153                    if self.config.pretty {
16154                        self.write_newline();
16155                        self.write_indent();
16156                    } else {
16157                        self.write_space();
16158                    }
16159                } else if self.config.pretty {
16160                    self.write_newline();
16161                    self.write_indent();
16162                }
16163                self.write_keyword("MEASURES");
16164                // In pretty mode, put each MEASURE on a new indented line
16165                if self.config.pretty {
16166                    self.indent_level += 1;
16167                    for (i, measure) in measures.iter().enumerate() {
16168                        if i > 0 {
16169                            self.write(",");
16170                        }
16171                        self.write_newline();
16172                        self.write_indent();
16173                        // Handle RUNNING/FINAL prefix
16174                        if let Some(semantics) = &measure.window_frame {
16175                            match semantics {
16176                                MatchRecognizeSemantics::Running => {
16177                                    self.write_keyword("RUNNING");
16178                                    self.write_space();
16179                                }
16180                                MatchRecognizeSemantics::Final => {
16181                                    self.write_keyword("FINAL");
16182                                    self.write_space();
16183                                }
16184                            }
16185                        }
16186                        self.generate_expression(&measure.this)?;
16187                    }
16188                    self.indent_level -= 1;
16189                } else {
16190                    self.write_space();
16191                    for (i, measure) in measures.iter().enumerate() {
16192                        if i > 0 {
16193                            self.write(", ");
16194                        }
16195                        // Handle RUNNING/FINAL prefix
16196                        if let Some(semantics) = &measure.window_frame {
16197                            match semantics {
16198                                MatchRecognizeSemantics::Running => {
16199                                    self.write_keyword("RUNNING");
16200                                    self.write_space();
16201                                }
16202                                MatchRecognizeSemantics::Final => {
16203                                    self.write_keyword("FINAL");
16204                                    self.write_space();
16205                                }
16206                            }
16207                        }
16208                        self.generate_expression(&measure.this)?;
16209                    }
16210                }
16211                needs_separator = true;
16212            }
16213        }
16214
16215        // Row semantics (ONE ROW PER MATCH, ALL ROWS PER MATCH, etc.)
16216        if let Some(rows) = &mr.rows {
16217            if needs_separator {
16218                if self.config.pretty {
16219                    self.write_newline();
16220                    self.write_indent();
16221                } else {
16222                    self.write_space();
16223                }
16224            } else if self.config.pretty {
16225                self.write_newline();
16226                self.write_indent();
16227            }
16228            match rows {
16229                MatchRecognizeRows::OneRowPerMatch => {
16230                    self.write_keyword("ONE ROW PER MATCH");
16231                }
16232                MatchRecognizeRows::AllRowsPerMatch => {
16233                    self.write_keyword("ALL ROWS PER MATCH");
16234                }
16235                MatchRecognizeRows::AllRowsPerMatchShowEmptyMatches => {
16236                    self.write_keyword("ALL ROWS PER MATCH SHOW EMPTY MATCHES");
16237                }
16238                MatchRecognizeRows::AllRowsPerMatchOmitEmptyMatches => {
16239                    self.write_keyword("ALL ROWS PER MATCH OMIT EMPTY MATCHES");
16240                }
16241                MatchRecognizeRows::AllRowsPerMatchWithUnmatchedRows => {
16242                    self.write_keyword("ALL ROWS PER MATCH WITH UNMATCHED ROWS");
16243                }
16244            }
16245            needs_separator = true;
16246        }
16247
16248        // AFTER MATCH SKIP
16249        if let Some(after) = &mr.after {
16250            if needs_separator {
16251                if self.config.pretty {
16252                    self.write_newline();
16253                    self.write_indent();
16254                } else {
16255                    self.write_space();
16256                }
16257            } else if self.config.pretty {
16258                self.write_newline();
16259                self.write_indent();
16260            }
16261            match after {
16262                MatchRecognizeAfter::PastLastRow => {
16263                    self.write_keyword("AFTER MATCH SKIP PAST LAST ROW");
16264                }
16265                MatchRecognizeAfter::ToNextRow => {
16266                    self.write_keyword("AFTER MATCH SKIP TO NEXT ROW");
16267                }
16268                MatchRecognizeAfter::ToFirst(ident) => {
16269                    self.write_keyword("AFTER MATCH SKIP TO FIRST");
16270                    self.write_space();
16271                    self.generate_identifier(ident)?;
16272                }
16273                MatchRecognizeAfter::ToLast(ident) => {
16274                    self.write_keyword("AFTER MATCH SKIP TO LAST");
16275                    self.write_space();
16276                    self.generate_identifier(ident)?;
16277                }
16278            }
16279            needs_separator = true;
16280        }
16281
16282        // PATTERN
16283        if let Some(pattern) = &mr.pattern {
16284            if needs_separator {
16285                if self.config.pretty {
16286                    self.write_newline();
16287                    self.write_indent();
16288                } else {
16289                    self.write_space();
16290                }
16291            } else if self.config.pretty {
16292                self.write_newline();
16293                self.write_indent();
16294            }
16295            self.write_keyword("PATTERN");
16296            self.write_space();
16297            self.write("(");
16298            self.write(pattern);
16299            self.write(")");
16300            needs_separator = true;
16301        }
16302
16303        // DEFINE
16304        if let Some(define) = &mr.define {
16305            if !define.is_empty() {
16306                if needs_separator {
16307                    if self.config.pretty {
16308                        self.write_newline();
16309                        self.write_indent();
16310                    } else {
16311                        self.write_space();
16312                    }
16313                } else if self.config.pretty {
16314                    self.write_newline();
16315                    self.write_indent();
16316                }
16317                self.write_keyword("DEFINE");
16318                // In pretty mode, put each DEFINE on a new indented line
16319                if self.config.pretty {
16320                    self.indent_level += 1;
16321                    for (i, (name, expr)) in define.iter().enumerate() {
16322                        if i > 0 {
16323                            self.write(",");
16324                        }
16325                        self.write_newline();
16326                        self.write_indent();
16327                        self.generate_identifier(name)?;
16328                        self.write(" AS ");
16329                        self.generate_expression(expr)?;
16330                    }
16331                    self.indent_level -= 1;
16332                } else {
16333                    self.write_space();
16334                    for (i, (name, expr)) in define.iter().enumerate() {
16335                        if i > 0 {
16336                            self.write(", ");
16337                        }
16338                        self.generate_identifier(name)?;
16339                        self.write(" AS ");
16340                        self.generate_expression(expr)?;
16341                    }
16342                }
16343            }
16344        }
16345
16346        if self.config.pretty {
16347            self.indent_level -= 1;
16348            self.write_newline();
16349        }
16350        self.write(")");
16351
16352        // Alias - only include AS if it was explicitly present in the input
16353        if let Some(alias) = &mr.alias {
16354            self.write(" ");
16355            if mr.alias_explicit_as {
16356                self.write_keyword("AS");
16357                self.write(" ");
16358            }
16359            self.generate_identifier(alias)?;
16360        }
16361
16362        Ok(())
16363    }
16364
16365    /// Generate a query hint /*+ ... */
16366    fn generate_hint(&mut self, hint: &Hint) -> Result<()> {
16367        use crate::dialects::DialectType;
16368
16369        // Output hints for dialects that support them, or when no dialect is specified (identity tests)
16370        let supports_hints = matches!(
16371            self.config.dialect,
16372            None |  // No dialect = preserve everything
16373            Some(DialectType::Oracle) | Some(DialectType::MySQL) |
16374            Some(DialectType::Spark) | Some(DialectType::Hive) |
16375            Some(DialectType::Databricks) | Some(DialectType::PostgreSQL)
16376        );
16377
16378        if !supports_hints || hint.expressions.is_empty() {
16379            return Ok(());
16380        }
16381
16382        // First, expand raw hint text into individual hint strings
16383        // This handles the case where the parser stored multiple hints as a single raw string
16384        let mut hint_strings: Vec<String> = Vec::new();
16385        for expr in &hint.expressions {
16386            match expr {
16387                HintExpression::Raw(text) => {
16388                    // Parse raw hint text into individual hint function calls
16389                    let parsed = self.parse_raw_hint_text(text);
16390                    hint_strings.extend(parsed);
16391                }
16392                _ => {
16393                    hint_strings.push(self.hint_expression_to_string(expr)?);
16394                }
16395            }
16396        }
16397
16398        // In pretty mode with multiple hints, always use multiline format
16399        // This matches Python sqlglot's behavior where expressions() with default dynamic=False
16400        // always joins with newlines in pretty mode
16401        let use_multiline = self.config.pretty && hint_strings.len() > 1;
16402
16403        if use_multiline {
16404            // Pretty print with each hint on its own line
16405            self.write(" /*+ ");
16406            for (i, hint_str) in hint_strings.iter().enumerate() {
16407                if i > 0 {
16408                    self.write_newline();
16409                    self.write("  "); // 2-space indent within hint block
16410                }
16411                self.write(hint_str);
16412            }
16413            self.write(" */");
16414        } else {
16415            // Single line format
16416            self.write(" /*+ ");
16417            let sep = match self.config.dialect {
16418                Some(DialectType::Spark) | Some(DialectType::Databricks) => ", ",
16419                _ => " ",
16420            };
16421            for (i, hint_str) in hint_strings.iter().enumerate() {
16422                if i > 0 {
16423                    self.write(sep);
16424                }
16425                self.write(hint_str);
16426            }
16427            self.write(" */");
16428        }
16429
16430        Ok(())
16431    }
16432
16433    /// Parse raw hint text into individual hint function calls
16434    /// e.g., "LEADING(a b) USE_NL(c)" -> ["LEADING(a b)", "USE_NL(c)"]
16435    /// If the hint contains unparseable content (like SQL keywords), return as single raw string
16436    fn parse_raw_hint_text(&self, text: &str) -> Vec<String> {
16437        let mut results = Vec::new();
16438        let mut chars = text.chars().peekable();
16439        let mut current = String::new();
16440        let mut paren_depth = 0;
16441        let mut has_unparseable_content = false;
16442        let mut position_after_last_function = 0;
16443        let mut char_position = 0;
16444
16445        while let Some(c) = chars.next() {
16446            char_position += c.len_utf8();
16447            match c {
16448                '(' => {
16449                    paren_depth += 1;
16450                    current.push(c);
16451                }
16452                ')' => {
16453                    paren_depth -= 1;
16454                    current.push(c);
16455                    // When we close the outer parenthesis, we've completed a hint function
16456                    if paren_depth == 0 {
16457                        let trimmed = current.trim().to_string();
16458                        if !trimmed.is_empty() {
16459                            // Format this hint for pretty printing if needed
16460                            let formatted = self.format_hint_function(&trimmed);
16461                            results.push(formatted);
16462                        }
16463                        current.clear();
16464                        position_after_last_function = char_position;
16465                    }
16466                }
16467                ' ' | '\t' | '\n' | ',' if paren_depth == 0 => {
16468                    // Space/comma/whitespace outside parentheses - skip
16469                }
16470                _ if paren_depth == 0 => {
16471                    // Character outside parentheses - accumulate for potential hint name
16472                    current.push(c);
16473                }
16474                _ => {
16475                    current.push(c);
16476                }
16477            }
16478        }
16479
16480        // Check if there's remaining text after the last function call
16481        let remaining_text = text[position_after_last_function..].trim();
16482        if !remaining_text.is_empty() {
16483            // Check if it looks like valid hint function names
16484            // Valid hint identifiers typically are uppercase alphanumeric with underscores
16485            // If we see multiple words without parens, it's likely unparseable
16486            let words: Vec<&str> = remaining_text.split_whitespace().collect();
16487            let looks_like_hint_functions = words.iter().all(|word| {
16488                // A valid hint name followed by opening paren, or a standalone uppercase identifier
16489                word.contains('(') || (word.chars().all(|c| c.is_ascii_uppercase() || c == '_'))
16490            });
16491
16492            if !looks_like_hint_functions && words.len() > 1 {
16493                has_unparseable_content = true;
16494            }
16495        }
16496
16497        // If we detected unparseable content (like SQL keywords), return the whole hint as-is
16498        if has_unparseable_content {
16499            return vec![text.trim().to_string()];
16500        }
16501
16502        // If we couldn't parse anything, return the original text as a single hint
16503        if results.is_empty() {
16504            results.push(text.trim().to_string());
16505        }
16506
16507        results
16508    }
16509
16510    /// Format a hint function for pretty printing
16511    /// e.g., "LEADING(aaa bbb ccc ddd)" -> multiline if args are too wide
16512    fn format_hint_function(&self, hint: &str) -> String {
16513        if !self.config.pretty {
16514            return hint.to_string();
16515        }
16516
16517        // Try to parse NAME(args) pattern
16518        if let Some(paren_pos) = hint.find('(') {
16519            if hint.ends_with(')') {
16520                let name = &hint[..paren_pos];
16521                let args_str = &hint[paren_pos + 1..hint.len() - 1];
16522
16523                // Parse arguments (space-separated for Oracle hints)
16524                let args: Vec<&str> = args_str.split_whitespace().collect();
16525
16526                // Calculate total width of arguments
16527                let total_args_width: usize =
16528                    args.iter().map(|s| s.len()).sum::<usize>() + args.len().saturating_sub(1); // spaces between args
16529
16530                // If too wide, format on multiple lines
16531                if total_args_width > self.config.max_text_width && !args.is_empty() {
16532                    let mut result = format!("{}(\n", name);
16533                    for arg in &args {
16534                        result.push_str("    "); // 4-space indent for args
16535                        result.push_str(arg);
16536                        result.push('\n');
16537                    }
16538                    result.push_str("  )"); // 2-space indent for closing paren
16539                    return result;
16540                }
16541            }
16542        }
16543
16544        hint.to_string()
16545    }
16546
16547    /// Convert a hint expression to a string, handling multiline formatting for long arguments
16548    fn hint_expression_to_string(&mut self, expr: &HintExpression) -> Result<String> {
16549        match expr {
16550            HintExpression::Function { name, args } => {
16551                // Generate each argument to a string
16552                let arg_strings: Vec<String> = args
16553                    .iter()
16554                    .map(|arg| {
16555                        let mut gen = Generator::with_arc_config(self.config.clone());
16556                        gen.generate_expression(arg)?;
16557                        Ok(gen.output)
16558                    })
16559                    .collect::<Result<Vec<_>>>()?;
16560
16561                // Oracle hints use space-separated arguments, not comma-separated
16562                let total_args_width: usize = arg_strings.iter().map(|s| s.len()).sum::<usize>()
16563                    + arg_strings.len().saturating_sub(1); // spaces between args
16564
16565                // Check if function args need multiline formatting
16566                // Use too_wide check for argument formatting
16567                let args_multiline =
16568                    self.config.pretty && total_args_width > self.config.max_text_width;
16569
16570                if args_multiline && !arg_strings.is_empty() {
16571                    // Multiline format for long argument lists
16572                    let mut result = format!("{}(\n", name);
16573                    for arg_str in &arg_strings {
16574                        result.push_str("    "); // 4-space indent for args
16575                        result.push_str(arg_str);
16576                        result.push('\n');
16577                    }
16578                    result.push_str("  )"); // 2-space indent for closing paren
16579                    Ok(result)
16580                } else {
16581                    // Single line format with space-separated args (Oracle style)
16582                    let args_str = arg_strings.join(" ");
16583                    Ok(format!("{}({})", name, args_str))
16584                }
16585            }
16586            HintExpression::Identifier(name) => Ok(name.clone()),
16587            HintExpression::Raw(text) => {
16588                // For pretty printing, try to format the raw text
16589                if self.config.pretty {
16590                    Ok(self.format_hint_function(text))
16591                } else {
16592                    Ok(text.clone())
16593                }
16594            }
16595        }
16596    }
16597
16598    fn generate_table(&mut self, table: &TableRef) -> Result<()> {
16599        // PostgreSQL ONLY modifier: prevents scanning child tables
16600        if table.only {
16601            self.write_keyword("ONLY");
16602            self.write_space();
16603        }
16604
16605        // Check for IDENTIFIER() (Snowflake) or OPENDATASOURCE(...).db.schema.table (TSQL)
16606        if let Some(ref identifier_func) = table.identifier_func {
16607            self.generate_expression(identifier_func)?;
16608            // If table name parts are present, emit .catalog.schema.name after the function
16609            if !table.name.name.is_empty() {
16610                if let Some(catalog) = &table.catalog {
16611                    self.write(".");
16612                    self.generate_identifier(catalog)?;
16613                }
16614                if let Some(schema) = &table.schema {
16615                    self.write(".");
16616                    self.generate_identifier(schema)?;
16617                }
16618                self.write(".");
16619                self.generate_identifier(&table.name)?;
16620            }
16621        } else {
16622            if let Some(catalog) = &table.catalog {
16623                self.generate_identifier(catalog)?;
16624                self.write(".");
16625            }
16626            if let Some(schema) = &table.schema {
16627                self.generate_identifier(schema)?;
16628                self.write(".");
16629            }
16630            self.generate_identifier(&table.name)?;
16631        }
16632
16633        // Output Snowflake CHANGES clause (before partition, includes its own AT/BEFORE/END)
16634        if let Some(changes) = &table.changes {
16635            self.write(" ");
16636            self.generate_changes(changes)?;
16637        }
16638
16639        // Output MySQL PARTITION clause: t1 PARTITION(p0, p1)
16640        if !table.partitions.is_empty() {
16641            self.write_space();
16642            self.write_keyword("PARTITION");
16643            self.write("(");
16644            for (i, partition) in table.partitions.iter().enumerate() {
16645                if i > 0 {
16646                    self.write(", ");
16647                }
16648                self.generate_identifier(partition)?;
16649            }
16650            self.write(")");
16651        }
16652
16653        // Output time travel clause: BEFORE (STATEMENT => ...) or AT (TIMESTAMP => ...)
16654        // Skip if CHANGES clause is present (CHANGES includes its own time travel)
16655        if table.changes.is_none() {
16656            if let Some(when) = &table.when {
16657                self.write_space();
16658                self.generate_historical_data(when)?;
16659            }
16660        }
16661
16662        // Output TSQL FOR SYSTEM_TIME temporal clause (before alias, except BigQuery)
16663        let system_time_post_alias = matches!(self.config.dialect, Some(DialectType::BigQuery));
16664        if !system_time_post_alias {
16665            if let Some(ref system_time) = table.system_time {
16666                self.write_space();
16667                self.write(system_time);
16668            }
16669        }
16670
16671        // Output Presto/Trino time travel: FOR VERSION AS OF / FOR TIMESTAMP AS OF
16672        if let Some(ref version) = table.version {
16673            self.write_space();
16674            self.generate_version(version)?;
16675        }
16676
16677        // When alias_post_tablesample is true, the order is: table TABLESAMPLE (...) alias
16678        // When alias_post_tablesample is false (default), the order is: table alias TABLESAMPLE (...)
16679        // Oracle, Hive, Spark use ALIAS_POST_TABLESAMPLE = true (alias comes after sample)
16680        let alias_post_tablesample = self.config.alias_post_tablesample;
16681
16682        if alias_post_tablesample {
16683            // TABLESAMPLE before alias (Oracle, Hive, Spark)
16684            self.generate_table_sample_clause(table)?;
16685        }
16686
16687        // Output table hints (TSQL: WITH (TABLOCK, INDEX(myindex), ...))
16688        // For SQLite, INDEXED BY hints come after the alias, so skip here
16689        let is_sqlite_hint = matches!(self.config.dialect, Some(DialectType::SQLite))
16690            && table.hints.iter().any(|h| {
16691                if let Expression::Identifier(id) = h {
16692                    id.name.starts_with("INDEXED BY") || id.name == "NOT INDEXED"
16693                } else {
16694                    false
16695                }
16696            });
16697        if !table.hints.is_empty() && !is_sqlite_hint {
16698            for hint in &table.hints {
16699                self.write_space();
16700                self.generate_expression(hint)?;
16701            }
16702        }
16703
16704        if let Some(alias) = &table.alias {
16705            self.write_space();
16706            // Output AS if it was explicitly present in the input, OR for certain dialects/cases
16707            // Generic mode and most dialects always use AS for table aliases
16708            let always_use_as = self.config.dialect.is_none()
16709                || matches!(
16710                    self.config.dialect,
16711                    Some(DialectType::Generic)
16712                        | Some(DialectType::PostgreSQL)
16713                        | Some(DialectType::Redshift)
16714                        | Some(DialectType::Snowflake)
16715                        | Some(DialectType::BigQuery)
16716                        | Some(DialectType::DuckDB)
16717                        | Some(DialectType::Presto)
16718                        | Some(DialectType::Trino)
16719                        | Some(DialectType::TSQL)
16720                        | Some(DialectType::Fabric)
16721                        | Some(DialectType::MySQL)
16722                        | Some(DialectType::Spark)
16723                        | Some(DialectType::Hive)
16724                        | Some(DialectType::SQLite)
16725                        | Some(DialectType::Drill)
16726                );
16727            let is_stage_ref = table.name.name.starts_with('@');
16728            // Oracle never uses AS for table aliases
16729            let suppress_as = matches!(self.config.dialect, Some(DialectType::Oracle));
16730            if !suppress_as && (table.alias_explicit_as || always_use_as || is_stage_ref) {
16731                self.write_keyword("AS");
16732                self.write_space();
16733            }
16734            self.generate_identifier(alias)?;
16735
16736            // Output column aliases if present: AS t(c1, c2)
16737            // Skip for dialects that don't support table alias columns (BigQuery, SQLite)
16738            if !table.column_aliases.is_empty() && self.config.supports_table_alias_columns {
16739                self.write("(");
16740                for (i, col_alias) in table.column_aliases.iter().enumerate() {
16741                    if i > 0 {
16742                        self.write(", ");
16743                    }
16744                    self.generate_identifier(col_alias)?;
16745                }
16746                self.write(")");
16747            }
16748        }
16749
16750        // BigQuery: FOR SYSTEM_TIME AS OF after alias
16751        if system_time_post_alias {
16752            if let Some(ref system_time) = table.system_time {
16753                self.write_space();
16754                self.write(system_time);
16755            }
16756        }
16757
16758        // For default behavior (alias_post_tablesample = false), output TABLESAMPLE after alias
16759        if !alias_post_tablesample {
16760            self.generate_table_sample_clause(table)?;
16761        }
16762
16763        // Output SQLite INDEXED BY / NOT INDEXED hints after alias
16764        if is_sqlite_hint {
16765            for hint in &table.hints {
16766                self.write_space();
16767                self.generate_expression(hint)?;
16768            }
16769        }
16770
16771        // ClickHouse FINAL modifier
16772        if table.final_ && matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
16773            self.write_space();
16774            self.write_keyword("FINAL");
16775        }
16776
16777        // Output trailing comments
16778        for comment in &table.trailing_comments {
16779            self.write_space();
16780            self.write_formatted_comment(comment);
16781        }
16782        // Note: leading_comments (from before table in FROM clause) are intentionally NOT
16783        // output here - they are output by the FROM/PIVOT generator after the full expression
16784
16785        Ok(())
16786    }
16787
16788    /// Helper to output TABLESAMPLE clause for a table reference
16789    fn generate_table_sample_clause(&mut self, table: &TableRef) -> Result<()> {
16790        if let Some(ref ts) = table.table_sample {
16791            self.write_space();
16792            if ts.is_using_sample {
16793                self.write_keyword("USING SAMPLE");
16794            } else {
16795                // Use the configured tablesample keyword (e.g., "TABLESAMPLE" or "SAMPLE")
16796                self.write_keyword(self.config.tablesample_keywords);
16797            }
16798            self.generate_sample_body(ts)?;
16799            // Seed for table-level sample - use dialect's configured keyword
16800            if let Some(ref seed) = ts.seed {
16801                self.write_space();
16802                self.write_keyword(self.config.tablesample_seed_keyword);
16803                self.write(" (");
16804                self.generate_expression(seed)?;
16805                self.write(")");
16806            }
16807        }
16808        Ok(())
16809    }
16810
16811    fn generate_stage_reference(&mut self, sr: &StageReference) -> Result<()> {
16812        // Output: '@stage_name/path' if quoted, or @stage_name/path otherwise
16813        // Optionally followed by (FILE_FORMAT => 'fmt', PATTERN => '*.csv')
16814
16815        if sr.quoted {
16816            self.write("'");
16817        }
16818
16819        self.write(&sr.name);
16820        if let Some(path) = &sr.path {
16821            self.write(path);
16822        }
16823
16824        if sr.quoted {
16825            self.write("'");
16826        }
16827
16828        // Output FILE_FORMAT and PATTERN if present
16829        let has_options = sr.file_format.is_some() || sr.pattern.is_some();
16830        if has_options {
16831            self.write(" (");
16832            let mut first = true;
16833
16834            if let Some(file_format) = &sr.file_format {
16835                if !first {
16836                    self.write(", ");
16837                }
16838                self.write_keyword("FILE_FORMAT");
16839                self.write(" => ");
16840                self.generate_expression(file_format)?;
16841                first = false;
16842            }
16843
16844            if let Some(pattern) = &sr.pattern {
16845                if !first {
16846                    self.write(", ");
16847                }
16848                self.write_keyword("PATTERN");
16849                self.write(" => '");
16850                self.write(pattern);
16851                self.write("'");
16852            }
16853
16854            self.write(")");
16855        }
16856        Ok(())
16857    }
16858
16859    fn generate_star(&mut self, star: &Star) -> Result<()> {
16860        use crate::dialects::DialectType;
16861
16862        if let Some(table) = &star.table {
16863            self.generate_identifier(table)?;
16864            self.write(".");
16865        }
16866        self.write("*");
16867
16868        // Generate EXCLUDE/EXCEPT clause based on dialect
16869        if let Some(except) = &star.except {
16870            if !except.is_empty() {
16871                self.write_space();
16872                // Use dialect-appropriate keyword
16873                match self.config.dialect {
16874                    Some(DialectType::BigQuery) => self.write_keyword("EXCEPT"),
16875                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => {
16876                        self.write_keyword("EXCLUDE")
16877                    }
16878                    _ => self.write_keyword("EXCEPT"), // Default to EXCEPT
16879                }
16880                self.write(" (");
16881                for (i, col) in except.iter().enumerate() {
16882                    if i > 0 {
16883                        self.write(", ");
16884                    }
16885                    self.generate_identifier(col)?;
16886                }
16887                self.write(")");
16888            }
16889        }
16890
16891        // Generate REPLACE clause
16892        if let Some(replace) = &star.replace {
16893            if !replace.is_empty() {
16894                self.write_space();
16895                self.write_keyword("REPLACE");
16896                self.write(" (");
16897                for (i, alias) in replace.iter().enumerate() {
16898                    if i > 0 {
16899                        self.write(", ");
16900                    }
16901                    self.generate_expression(&alias.this)?;
16902                    self.write_space();
16903                    self.write_keyword("AS");
16904                    self.write_space();
16905                    self.generate_identifier(&alias.alias)?;
16906                }
16907                self.write(")");
16908            }
16909        }
16910
16911        // Generate RENAME clause (Snowflake specific)
16912        if let Some(rename) = &star.rename {
16913            if !rename.is_empty() {
16914                self.write_space();
16915                self.write_keyword("RENAME");
16916                self.write(" (");
16917                for (i, (old_name, new_name)) in rename.iter().enumerate() {
16918                    if i > 0 {
16919                        self.write(", ");
16920                    }
16921                    self.generate_identifier(old_name)?;
16922                    self.write_space();
16923                    self.write_keyword("AS");
16924                    self.write_space();
16925                    self.generate_identifier(new_name)?;
16926                }
16927                self.write(")");
16928            }
16929        }
16930
16931        // Output trailing comments
16932        for comment in &star.trailing_comments {
16933            self.write_space();
16934            self.write_formatted_comment(comment);
16935        }
16936
16937        Ok(())
16938    }
16939
16940    /// Generate Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'}
16941    fn generate_braced_wildcard(&mut self, expr: &Expression) -> Result<()> {
16942        self.write("{");
16943        match expr {
16944            Expression::Star(star) => {
16945                // Generate the star (table.* or just * with optional EXCLUDE)
16946                self.generate_star(star)?;
16947            }
16948            Expression::ILike(ilike) => {
16949                // {* ILIKE 'pattern'} syntax
16950                self.generate_expression(&ilike.left)?;
16951                self.write_space();
16952                self.write_keyword("ILIKE");
16953                self.write_space();
16954                self.generate_expression(&ilike.right)?;
16955            }
16956            _ => {
16957                self.generate_expression(expr)?;
16958            }
16959        }
16960        self.write("}");
16961        Ok(())
16962    }
16963
16964    fn generate_alias(&mut self, alias: &Alias) -> Result<()> {
16965        // Generate inner expression, but skip trailing comments if they're in pre_alias_comments
16966        // to avoid duplication (comments are captured as both Column.trailing_comments
16967        // and Alias.pre_alias_comments during parsing)
16968        match &alias.this {
16969            Expression::Column(col) => {
16970                // Generate column without trailing comments - they're in pre_alias_comments
16971                if let Some(table) = &col.table {
16972                    self.generate_identifier(table)?;
16973                    self.write(".");
16974                }
16975                self.generate_identifier(&col.name)?;
16976            }
16977            _ => {
16978                self.generate_expression(&alias.this)?;
16979            }
16980        }
16981
16982        // Handle pre-alias comments: when there are no trailing_comments, sqlglot
16983        // moves pre-alias comments to after the alias. When there are also trailing_comments,
16984        // keep pre-alias comments in their original position (between expression and AS).
16985        if !alias.pre_alias_comments.is_empty() && !alias.trailing_comments.is_empty() {
16986            for comment in &alias.pre_alias_comments {
16987                self.write_space();
16988                self.write_formatted_comment(comment);
16989            }
16990        }
16991
16992        use crate::dialects::DialectType;
16993
16994        // Determine if we should skip AS keyword for table-valued function aliases
16995        // Oracle and some other dialects don't use AS for table aliases
16996        // Note: We specifically use TableFromRows here, NOT Function, because Function
16997        // matches regular functions like MATCH_NUMBER() which should include the AS keyword.
16998        // TableFromRows represents TABLE(expr) constructs which are actual table-valued functions.
16999        let is_table_source = matches!(
17000            &alias.this,
17001            Expression::JSONTable(_)
17002                | Expression::XMLTable(_)
17003                | Expression::TableFromRows(_)
17004                | Expression::Unnest(_)
17005                | Expression::MatchRecognize(_)
17006                | Expression::Select(_)
17007                | Expression::Subquery(_)
17008                | Expression::Paren(_)
17009        );
17010        let dialect_skips_table_alias_as = matches!(self.config.dialect, Some(DialectType::Oracle));
17011        let skip_as = is_table_source && dialect_skips_table_alias_as;
17012
17013        self.write_space();
17014        if !skip_as {
17015            if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
17016                if let Some(ref alias_keyword) = alias.alias_keyword {
17017                    self.write(alias_keyword);
17018                } else {
17019                    self.write_keyword("AS");
17020                }
17021            } else {
17022                self.write_keyword("AS");
17023            }
17024            self.write_space();
17025        }
17026
17027        // BigQuery doesn't support column aliases in table aliases: AS t(c1, c2)
17028        let skip_column_aliases = matches!(self.config.dialect, Some(DialectType::BigQuery));
17029
17030        // Check if we have column aliases only (no table alias name)
17031        if alias.alias.is_empty() && !alias.column_aliases.is_empty() && !skip_column_aliases {
17032            // Generate AS (col1, col2, ...)
17033            self.write("(");
17034            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
17035                if i > 0 {
17036                    self.write(", ");
17037                }
17038                self.generate_alias_identifier(col_alias)?;
17039            }
17040            self.write(")");
17041        } else if !alias.column_aliases.is_empty() && !skip_column_aliases {
17042            // Generate AS alias(col1, col2, ...)
17043            self.generate_alias_identifier(&alias.alias)?;
17044            self.write("(");
17045            for (i, col_alias) in alias.column_aliases.iter().enumerate() {
17046                if i > 0 {
17047                    self.write(", ");
17048                }
17049                self.generate_alias_identifier(col_alias)?;
17050            }
17051            self.write(")");
17052        } else {
17053            // Simple alias (or BigQuery without column aliases)
17054            self.generate_alias_identifier(&alias.alias)?;
17055        }
17056
17057        // Output trailing comments (comments after the alias)
17058        for comment in &alias.trailing_comments {
17059            self.write_space();
17060            self.write_formatted_comment(comment);
17061        }
17062
17063        // Output pre-alias comments: when there are no trailing_comments, sqlglot
17064        // moves pre-alias comments to after the alias. When there are trailing_comments,
17065        // the pre-alias comments were already lost (consumed as column trailing comments
17066        // that were then used as pre_alias_comments). We always emit them after alias.
17067        if alias.trailing_comments.is_empty() {
17068            for comment in &alias.pre_alias_comments {
17069                self.write_space();
17070                self.write_formatted_comment(comment);
17071            }
17072        }
17073
17074        Ok(())
17075    }
17076
17077    fn generate_cast(&mut self, cast: &Cast) -> Result<()> {
17078        use crate::dialects::DialectType;
17079
17080        // SingleStore uses :> syntax
17081        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
17082            self.generate_expression(&cast.this)?;
17083            self.write(" :> ");
17084            self.generate_data_type(&cast.to)?;
17085            return Ok(());
17086        }
17087
17088        // Teradata: CAST(x AS FORMAT 'fmt') (no data type)
17089        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
17090            let is_unknown_type = matches!(cast.to, DataType::Unknown)
17091                || matches!(cast.to, DataType::Custom { ref name } if name.is_empty());
17092            if is_unknown_type {
17093                if let Some(format) = &cast.format {
17094                    self.write_keyword("CAST");
17095                    self.write("(");
17096                    self.generate_expression(&cast.this)?;
17097                    self.write_space();
17098                    self.write_keyword("AS");
17099                    self.write_space();
17100                    self.write_keyword("FORMAT");
17101                    self.write_space();
17102                    self.generate_expression(format)?;
17103                    self.write(")");
17104                    return Ok(());
17105                }
17106            }
17107        }
17108
17109        // Oracle: CAST(x AS DATE/TIMESTAMP ..., 'format') -> TO_DATE/TO_TIMESTAMP(x, 'format')
17110        // This follows Python sqlglot's behavior of transforming CAST with format to native functions
17111        if matches!(self.config.dialect, Some(DialectType::Oracle)) {
17112            if let Some(format) = &cast.format {
17113                // Check if target type is DATE or TIMESTAMP
17114                let is_date = matches!(cast.to, DataType::Date);
17115                let is_timestamp = matches!(cast.to, DataType::Timestamp { .. });
17116
17117                if is_date || is_timestamp {
17118                    let func_name = if is_date { "TO_DATE" } else { "TO_TIMESTAMP" };
17119                    self.write_keyword(func_name);
17120                    self.write("(");
17121                    self.generate_expression(&cast.this)?;
17122                    self.write(", ");
17123
17124                    // Normalize format string for Oracle (HH -> HH12)
17125                    // Oracle HH is 12-hour format, same as HH12. For clarity, Python sqlglot uses HH12.
17126                    if let Expression::Literal(lit) = format.as_ref() {
17127                        if let Literal::String(fmt_str) = lit.as_ref() {
17128                            let normalized = self.normalize_oracle_format(fmt_str);
17129                            self.write("'");
17130                            self.write(&normalized);
17131                            self.write("'");
17132                        }
17133                    } else {
17134                        self.generate_expression(format)?;
17135                    }
17136
17137                    self.write(")");
17138                    return Ok(());
17139                }
17140            }
17141        }
17142
17143        // BigQuery: CAST(ARRAY[...] AS ARRAY<T>) -> ARRAY<T>[...]
17144        // This preserves sqlglot's typed inline array literal output.
17145        if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
17146            if let Expression::Array(arr) = &cast.this {
17147                self.generate_data_type(&cast.to)?;
17148                // Output just the bracket content [values] without the ARRAY prefix
17149                self.write("[");
17150                for (i, expr) in arr.expressions.iter().enumerate() {
17151                    if i > 0 {
17152                        self.write(", ");
17153                    }
17154                    self.generate_expression(expr)?;
17155                }
17156                self.write("]");
17157                return Ok(());
17158            }
17159            if matches!(&cast.this, Expression::ArrayFunc(_)) {
17160                self.generate_data_type(&cast.to)?;
17161                self.generate_expression(&cast.this)?;
17162                return Ok(());
17163            }
17164        }
17165
17166        // DuckDB/Presto/Trino: When CAST(Struct([unnamed]) AS STRUCT(...)),
17167        // convert the inner Struct to ROW(values...) format
17168        if matches!(
17169            self.config.dialect,
17170            Some(DialectType::DuckDB) | Some(DialectType::Presto) | Some(DialectType::Trino)
17171        ) {
17172            if let Expression::Struct(ref s) = cast.this {
17173                let all_unnamed = s.fields.iter().all(|(name, _)| name.is_none());
17174                if all_unnamed && matches!(cast.to, DataType::Struct { .. }) {
17175                    self.write_keyword("CAST");
17176                    self.write("(");
17177                    self.generate_struct_as_row(s)?;
17178                    self.write_space();
17179                    self.write_keyword("AS");
17180                    self.write_space();
17181                    self.generate_data_type(&cast.to)?;
17182                    self.write(")");
17183                    return Ok(());
17184                }
17185            }
17186        }
17187
17188        // Determine if we should use :: syntax based on dialect
17189        // PostgreSQL prefers :: for identity, most others prefer CAST()
17190        let use_double_colon = cast.double_colon_syntax && self.dialect_prefers_double_colon();
17191
17192        if use_double_colon {
17193            // PostgreSQL :: syntax: expr::type
17194            self.generate_expression(&cast.this)?;
17195            self.write("::");
17196            self.generate_data_type(&cast.to)?;
17197        } else {
17198            // Standard CAST() syntax
17199            self.write_keyword("CAST");
17200            self.write("(");
17201            self.generate_expression(&cast.this)?;
17202            self.write_space();
17203            self.write_keyword("AS");
17204            self.write_space();
17205            // For MySQL/SingleStore/TiDB, map text/blob variant types to CHAR in CAST
17206            // This matches Python sqlglot's CAST_MAPPING behavior
17207            if matches!(
17208                self.config.dialect,
17209                Some(DialectType::MySQL) | Some(DialectType::SingleStore) | Some(DialectType::TiDB)
17210            ) {
17211                match &cast.to {
17212                    DataType::Custom { ref name } => {
17213                        if name.eq_ignore_ascii_case("LONGTEXT")
17214                            || name.eq_ignore_ascii_case("MEDIUMTEXT")
17215                            || name.eq_ignore_ascii_case("TINYTEXT")
17216                            || name.eq_ignore_ascii_case("LONGBLOB")
17217                            || name.eq_ignore_ascii_case("MEDIUMBLOB")
17218                            || name.eq_ignore_ascii_case("TINYBLOB")
17219                        {
17220                            self.write_keyword("CHAR");
17221                        } else {
17222                            self.generate_data_type(&cast.to)?;
17223                        }
17224                    }
17225                    DataType::VarChar { length, .. } => {
17226                        // MySQL CAST: VARCHAR -> CHAR
17227                        self.write_keyword("CHAR");
17228                        if let Some(n) = length {
17229                            self.write(&format!("({})", n));
17230                        }
17231                    }
17232                    DataType::Text => {
17233                        // MySQL CAST: TEXT -> CHAR
17234                        self.write_keyword("CHAR");
17235                    }
17236                    DataType::Timestamp {
17237                        precision,
17238                        timezone: false,
17239                    } => {
17240                        // MySQL CAST: TIMESTAMP -> DATETIME
17241                        self.write_keyword("DATETIME");
17242                        if let Some(p) = precision {
17243                            self.write(&format!("({})", p));
17244                        }
17245                    }
17246                    _ => {
17247                        self.generate_data_type(&cast.to)?;
17248                    }
17249                }
17250            } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
17251                // Snowflake CAST: STRING -> VARCHAR
17252                match &cast.to {
17253                    DataType::String { length } => {
17254                        self.write_keyword("VARCHAR");
17255                        if let Some(n) = length {
17256                            self.write(&format!("({})", n));
17257                        }
17258                    }
17259                    _ => {
17260                        self.generate_data_type(&cast.to)?;
17261                    }
17262                }
17263            } else {
17264                self.generate_data_type(&cast.to)?;
17265            }
17266
17267            // Output DEFAULT ... ON CONVERSION ERROR clause if present (Oracle)
17268            if let Some(default) = &cast.default {
17269                self.write_space();
17270                self.write_keyword("DEFAULT");
17271                self.write_space();
17272                self.generate_expression(default)?;
17273                self.write_space();
17274                self.write_keyword("ON");
17275                self.write_space();
17276                self.write_keyword("CONVERSION");
17277                self.write_space();
17278                self.write_keyword("ERROR");
17279            }
17280
17281            // Output FORMAT clause if present (BigQuery: CAST(x AS STRING FORMAT 'format'))
17282            // For Oracle with comma-separated format: CAST(x AS DATE DEFAULT NULL ON CONVERSION ERROR, 'format')
17283            if let Some(format) = &cast.format {
17284                // Check if Oracle dialect - use comma syntax
17285                if matches!(
17286                    self.config.dialect,
17287                    Some(crate::dialects::DialectType::Oracle)
17288                ) {
17289                    self.write(", ");
17290                } else {
17291                    self.write_space();
17292                    self.write_keyword("FORMAT");
17293                    self.write_space();
17294                }
17295                self.generate_expression(format)?;
17296            }
17297
17298            self.write(")");
17299            // Output trailing comments
17300            for comment in &cast.trailing_comments {
17301                self.write_space();
17302                self.write_formatted_comment(comment);
17303            }
17304        }
17305        Ok(())
17306    }
17307
17308    /// Generate a Struct as ROW(values...) format, recursively converting inner Struct to ROW too.
17309    /// Used for DuckDB/Presto/Trino CAST(Struct AS STRUCT(...)) context.
17310    fn generate_struct_as_row(&mut self, s: &crate::expressions::Struct) -> Result<()> {
17311        self.write_keyword("ROW");
17312        self.write("(");
17313        for (i, (_, expr)) in s.fields.iter().enumerate() {
17314            if i > 0 {
17315                self.write(", ");
17316            }
17317            // Recursively convert inner Struct to ROW format
17318            if let Expression::Struct(ref inner_s) = expr {
17319                self.generate_struct_as_row(inner_s)?;
17320            } else {
17321                self.generate_expression(expr)?;
17322            }
17323        }
17324        self.write(")");
17325        Ok(())
17326    }
17327
17328    /// Normalize Oracle date/time format strings
17329    /// HH -> HH12 (both are 12-hour format, but Python sqlglot prefers explicit HH12)
17330    fn normalize_oracle_format(&self, format: &str) -> String {
17331        // Replace standalone HH with HH12 (but not HH12 or HH24)
17332        // We need to be careful not to replace HH12 -> HH1212 or HH24 -> HH1224
17333        let mut result = String::new();
17334        let chars: Vec<char> = format.chars().collect();
17335        let mut i = 0;
17336
17337        while i < chars.len() {
17338            if i + 1 < chars.len() && chars[i] == 'H' && chars[i + 1] == 'H' {
17339                // Check what follows HH
17340                if i + 2 < chars.len() {
17341                    let next = chars[i + 2];
17342                    if next == '1' || next == '2' {
17343                        // This is HH12 or HH24, keep as is
17344                        result.push('H');
17345                        result.push('H');
17346                        i += 2;
17347                        continue;
17348                    }
17349                }
17350                // Standalone HH -> HH12
17351                result.push_str("HH12");
17352                i += 2;
17353            } else {
17354                result.push(chars[i]);
17355                i += 1;
17356            }
17357        }
17358
17359        result
17360    }
17361
17362    /// Check if the current dialect prefers :: cast syntax
17363    /// Preserve ClickHouse's native `::` shorthand when the parser saw it.
17364    fn dialect_prefers_double_colon(&self) -> bool {
17365        matches!(self.config.dialect, Some(DialectType::ClickHouse))
17366    }
17367
17368    /// Generate MOD function - uses % operator for dialects that prefer or require it.
17369    fn generate_mod_func(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17370        use crate::dialects::DialectType;
17371
17372        // Several dialects prefer or require x % y instead of MOD(x, y).
17373        let use_percent_operator = matches!(
17374            self.config.dialect,
17375            Some(DialectType::Snowflake)
17376                | Some(DialectType::MySQL)
17377                | Some(DialectType::Presto)
17378                | Some(DialectType::Trino)
17379                | Some(DialectType::PostgreSQL)
17380                | Some(DialectType::DuckDB)
17381                | Some(DialectType::Hive)
17382                | Some(DialectType::Spark)
17383                | Some(DialectType::Databricks)
17384                | Some(DialectType::Athena)
17385                | Some(DialectType::TSQL)
17386                | Some(DialectType::Fabric)
17387        );
17388
17389        if use_percent_operator {
17390            // MOD(a, b) treats both arguments as grouped expressions. When
17391            // lowering to an infix operator, keep binary arguments grouped.
17392            let needs_paren = |e: &Expression| {
17393                matches!(
17394                    e,
17395                    Expression::Add(_)
17396                        | Expression::Sub(_)
17397                        | Expression::Mul(_)
17398                        | Expression::Div(_)
17399                        | Expression::Mod(_)
17400                        | Expression::ModFunc(_)
17401                )
17402            };
17403            if needs_paren(&f.this) {
17404                self.write("(");
17405                self.generate_expression(&f.this)?;
17406                self.write(")");
17407            } else {
17408                self.generate_expression(&f.this)?;
17409            }
17410            self.write(" % ");
17411            if needs_paren(&f.expression) {
17412                self.write("(");
17413                self.generate_expression(&f.expression)?;
17414                self.write(")");
17415            } else {
17416                self.generate_expression(&f.expression)?;
17417            }
17418            Ok(())
17419        } else {
17420            self.generate_binary_func("MOD", &f.this, &f.expression)
17421        }
17422    }
17423
17424    /// Generate IFNULL - uses COALESCE for Snowflake, IFNULL for others
17425    fn generate_ifnull(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17426        use crate::dialects::DialectType;
17427
17428        // Snowflake normalizes IFNULL to COALESCE
17429        let func_name = match self.config.dialect {
17430            Some(DialectType::Snowflake) => "COALESCE",
17431            _ => "IFNULL",
17432        };
17433
17434        self.generate_binary_func(func_name, &f.this, &f.expression)
17435    }
17436
17437    /// Generate NVL - preserves original name if available, otherwise uses dialect-specific output
17438    fn generate_nvl(&mut self, f: &crate::expressions::BinaryFunc) -> Result<()> {
17439        // Use original function name if preserved (for identity tests)
17440        if let Some(ref original_name) = f.original_name {
17441            return self.generate_binary_func(original_name, &f.this, &f.expression);
17442        }
17443
17444        // Otherwise, use dialect-specific function names
17445        use crate::dialects::DialectType;
17446        let func_name = match self.config.dialect {
17447            Some(DialectType::Snowflake)
17448            | Some(DialectType::ClickHouse)
17449            | Some(DialectType::PostgreSQL)
17450            | Some(DialectType::Presto)
17451            | Some(DialectType::Trino)
17452            | Some(DialectType::Athena)
17453            | Some(DialectType::DuckDB)
17454            | Some(DialectType::BigQuery)
17455            | Some(DialectType::Spark)
17456            | Some(DialectType::Databricks)
17457            | Some(DialectType::Hive) => "COALESCE",
17458            Some(DialectType::MySQL)
17459            | Some(DialectType::Doris)
17460            | Some(DialectType::StarRocks)
17461            | Some(DialectType::SingleStore)
17462            | Some(DialectType::TiDB) => "IFNULL",
17463            _ => "NVL",
17464        };
17465
17466        self.generate_binary_func(func_name, &f.this, &f.expression)
17467    }
17468
17469    /// Generate STDDEV_SAMP - uses STDDEV for Snowflake, STDDEV_SAMP for others
17470    fn generate_stddev_samp(&mut self, f: &crate::expressions::AggFunc) -> Result<()> {
17471        use crate::dialects::DialectType;
17472
17473        // Snowflake normalizes STDDEV_SAMP to STDDEV
17474        let func_name = match self.config.dialect {
17475            Some(DialectType::Snowflake) => "STDDEV",
17476            _ => "STDDEV_SAMP",
17477        };
17478
17479        self.generate_agg_func(func_name, f)
17480    }
17481
17482    fn generate_collation(&mut self, coll: &CollationExpr) -> Result<()> {
17483        self.generate_expression(&coll.this)?;
17484        self.write_space();
17485        self.write_keyword("COLLATE");
17486        self.write_space();
17487        if coll.quoted {
17488            // Single-quoted string: COLLATE 'de_DE'
17489            self.write("'");
17490            self.write(&coll.collation);
17491            self.write("'");
17492        } else if coll.double_quoted {
17493            // Double-quoted identifier: COLLATE "de_DE"
17494            self.write("\"");
17495            self.write(&coll.collation);
17496            self.write("\"");
17497        } else {
17498            // Unquoted identifier: COLLATE de_DE
17499            self.write(&coll.collation);
17500        }
17501        Ok(())
17502    }
17503
17504    fn generate_case(&mut self, case: &Case) -> Result<()> {
17505        // In pretty mode, decide whether to expand based on total text width
17506        let multiline_case = if self.config.pretty {
17507            // Build the flat representation to check width
17508            let mut statements: Vec<String> = Vec::new();
17509            let operand_str = if let Some(operand) = &case.operand {
17510                let s = self.generate_to_string(operand)?;
17511                statements.push(format!("CASE {}", s));
17512                s
17513            } else {
17514                statements.push("CASE".to_string());
17515                String::new()
17516            };
17517            let _ = operand_str;
17518            for (condition, result) in &case.whens {
17519                statements.push(format!("WHEN {}", self.generate_to_string(condition)?));
17520                statements.push(format!("THEN {}", self.generate_to_string(result)?));
17521            }
17522            if let Some(else_) = &case.else_ {
17523                statements.push(format!("ELSE {}", self.generate_to_string(else_)?));
17524            }
17525            statements.push("END".to_string());
17526            self.too_wide(&statements)
17527        } else {
17528            false
17529        };
17530
17531        self.write_keyword("CASE");
17532        if let Some(operand) = &case.operand {
17533            self.write_space();
17534            self.generate_expression(operand)?;
17535        }
17536        if multiline_case {
17537            self.indent_level += 1;
17538        }
17539        for (condition, result) in &case.whens {
17540            if multiline_case {
17541                self.write_newline();
17542                self.write_indent();
17543            } else {
17544                self.write_space();
17545            }
17546            self.write_keyword("WHEN");
17547            self.write_space();
17548            self.generate_expression(condition)?;
17549            if multiline_case {
17550                self.write_newline();
17551                self.write_indent();
17552            } else {
17553                self.write_space();
17554            }
17555            self.write_keyword("THEN");
17556            self.write_space();
17557            self.generate_expression(result)?;
17558        }
17559        if let Some(else_) = &case.else_ {
17560            if multiline_case {
17561                self.write_newline();
17562                self.write_indent();
17563            } else {
17564                self.write_space();
17565            }
17566            self.write_keyword("ELSE");
17567            self.write_space();
17568            self.generate_expression(else_)?;
17569        }
17570        if multiline_case {
17571            self.indent_level -= 1;
17572            self.write_newline();
17573            self.write_indent();
17574        } else {
17575            self.write_space();
17576        }
17577        self.write_keyword("END");
17578        // Emit any comments that were attached to the CASE keyword
17579        for comment in &case.comments {
17580            self.write(" ");
17581            self.write_formatted_comment(comment);
17582        }
17583        Ok(())
17584    }
17585
17586    fn generate_function(&mut self, func: &Function) -> Result<()> {
17587        // Normalize function name based on dialect settings
17588        let normalized_name = self.normalize_func_name(&func.name);
17589
17590        // DuckDB: ARRAY_CONSTRUCT_COMPACT(a, b, c) -> LIST_FILTER([a, b, c], _u -> NOT _u IS NULL)
17591        if matches!(self.config.dialect, Some(DialectType::DuckDB))
17592            && func.name.eq_ignore_ascii_case("ARRAY_CONSTRUCT_COMPACT")
17593        {
17594            self.write("LIST_FILTER(");
17595            self.write("[");
17596            for (i, arg) in func.args.iter().enumerate() {
17597                if i > 0 {
17598                    self.write(", ");
17599                }
17600                self.generate_expression(arg)?;
17601            }
17602            self.write("], _u -> NOT _u IS NULL)");
17603            return Ok(());
17604        }
17605
17606        // Snowflake fixtures expect TO_VARIANT applied to arrays to keep ARRAY_CONSTRUCT(...)
17607        // rather than bracket-array syntax.
17608        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17609            && func.name.eq_ignore_ascii_case("TO_VARIANT")
17610            && func.args.len() == 1
17611        {
17612            let array_expressions = match &func.args[0] {
17613                Expression::ArrayFunc(arr) => Some(&arr.expressions),
17614                Expression::Array(arr) => Some(&arr.expressions),
17615                _ => None,
17616            };
17617            if let Some(expressions) = array_expressions {
17618                self.write_keyword("TO_VARIANT");
17619                self.write("(");
17620                self.write_keyword("ARRAY_CONSTRUCT");
17621                self.write("(");
17622                for (i, arg) in expressions.iter().enumerate() {
17623                    if i > 0 {
17624                        self.write(", ");
17625                    }
17626                    self.generate_expression(arg)?;
17627                }
17628                self.write(")");
17629                self.write(")");
17630                return Ok(());
17631            }
17632        }
17633
17634        // STRUCT function: BigQuery STRUCT('Alice' AS name, 85 AS score) -> dialect-specific
17635        if func.name.eq_ignore_ascii_case("STRUCT")
17636            && !matches!(
17637                self.config.dialect,
17638                Some(DialectType::BigQuery)
17639                    | Some(DialectType::Spark)
17640                    | Some(DialectType::Databricks)
17641                    | Some(DialectType::Hive)
17642                    | None
17643            )
17644        {
17645            return self.generate_struct_function_cross_dialect(func);
17646        }
17647
17648        // SingleStore: __SS_JSON_PATH_QMARK__(expr, key) -> expr::?key
17649        // This is an internal marker function for ::? JSON path syntax
17650        if func.name.eq_ignore_ascii_case("__SS_JSON_PATH_QMARK__") && func.args.len() == 2 {
17651            self.generate_expression(&func.args[0])?;
17652            self.write("::?");
17653            // Extract the key from the string literal
17654            if let Expression::Literal(lit) = &func.args[1] {
17655                if let crate::expressions::Literal::String(key) = lit.as_ref() {
17656                    self.write(key);
17657                }
17658            } else {
17659                self.generate_expression(&func.args[1])?;
17660            }
17661            return Ok(());
17662        }
17663
17664        // PostgreSQL: __PG_BITWISE_XOR__(a, b) -> a # b
17665        if func.name.eq_ignore_ascii_case("__PG_BITWISE_XOR__") && func.args.len() == 2 {
17666            self.generate_expression(&func.args[0])?;
17667            self.write(" # ");
17668            self.generate_expression(&func.args[1])?;
17669            return Ok(());
17670        }
17671
17672        // Spark/Hive family: unwrap TRY(expr) since these dialects don't emit TRY as a scalar wrapper.
17673        if matches!(
17674            self.config.dialect,
17675            Some(DialectType::Spark | DialectType::Databricks | DialectType::Hive)
17676        ) && func.name.eq_ignore_ascii_case("TRY")
17677            && func.args.len() == 1
17678        {
17679            self.generate_expression(&func.args[0])?;
17680            return Ok(());
17681        }
17682
17683        // ClickHouse normalization: toStartOfDay(x) -> dateTrunc('DAY', x)
17684        if self.config.dialect == Some(DialectType::ClickHouse)
17685            && func.name.eq_ignore_ascii_case("TOSTARTOFDAY")
17686            && func.args.len() == 1
17687        {
17688            self.write("dateTrunc('DAY', ");
17689            self.generate_expression(&func.args[0])?;
17690            self.write(")");
17691            return Ok(());
17692        }
17693
17694        // ClickHouse uses dateTrunc casing.
17695        if self.config.dialect == Some(DialectType::ClickHouse)
17696            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17697            && func.args.len() == 2
17698        {
17699            self.write("dateTrunc(");
17700            self.generate_expression(&func.args[0])?;
17701            self.write(", ");
17702            self.generate_expression(&func.args[1])?;
17703            self.write(")");
17704            return Ok(());
17705        }
17706
17707        // Presto-family dialects spell SUBSTRING as SUBSTR in SQLGlot outputs.
17708        if matches!(
17709            self.config.dialect,
17710            Some(DialectType::Presto | DialectType::Trino | DialectType::Athena)
17711        ) && func.name.eq_ignore_ascii_case("SUBSTRING")
17712        {
17713            self.write_keyword("SUBSTR");
17714            self.write("(");
17715            for (i, arg) in func.args.iter().enumerate() {
17716                if i > 0 {
17717                    self.write(", ");
17718                }
17719                self.generate_expression(arg)?;
17720            }
17721            self.write(")");
17722            return Ok(());
17723        }
17724
17725        if self.config.dialect == Some(DialectType::Snowflake)
17726            && func.name.eq_ignore_ascii_case("LIST_DISTINCT")
17727            && func.args.len() == 1
17728        {
17729            self.write_keyword("ARRAY_DISTINCT");
17730            self.write("(");
17731            self.write_keyword("ARRAY_COMPACT");
17732            self.write("(");
17733            self.generate_expression(&func.args[0])?;
17734            self.write("))");
17735            return Ok(());
17736        }
17737
17738        if self.config.dialect == Some(DialectType::Snowflake)
17739            && func.name.eq_ignore_ascii_case("LIST")
17740            && func.args.len() == 1
17741            && !matches!(func.args.first(), Some(Expression::Select(_)))
17742        {
17743            self.write_keyword("ARRAY_AGG");
17744            self.write("(");
17745            self.generate_expression(&func.args[0])?;
17746            self.write(")");
17747            return Ok(());
17748        }
17749
17750        // Redshift: CONCAT(a, b, ...) -> a || b || ...
17751        if self.config.dialect == Some(DialectType::Redshift)
17752            && func.name.eq_ignore_ascii_case("CONCAT")
17753            && func.args.len() >= 2
17754        {
17755            for (i, arg) in func.args.iter().enumerate() {
17756                if i > 0 {
17757                    self.write(" || ");
17758                }
17759                self.generate_expression(arg)?;
17760            }
17761            return Ok(());
17762        }
17763
17764        // Redshift: CONCAT_WS(delim, a, b, c) -> a || delim || b || delim || c
17765        if self.config.dialect == Some(DialectType::Redshift)
17766            && func.name.eq_ignore_ascii_case("CONCAT_WS")
17767            && func.args.len() >= 2
17768        {
17769            let sep = &func.args[0];
17770            for (i, arg) in func.args.iter().skip(1).enumerate() {
17771                if i > 0 {
17772                    self.write(" || ");
17773                    self.generate_expression(sep)?;
17774                    self.write(" || ");
17775                }
17776                self.generate_expression(arg)?;
17777            }
17778            return Ok(());
17779        }
17780
17781        // Redshift: DATEDIFF/DATE_DIFF(unit, start, end) -> DATEDIFF(UNIT, start, end)
17782        // Unit should be unquoted uppercase identifier
17783        if self.config.dialect == Some(DialectType::Redshift)
17784            && (func.name.eq_ignore_ascii_case("DATEDIFF")
17785                || func.name.eq_ignore_ascii_case("DATE_DIFF"))
17786            && func.args.len() == 3
17787        {
17788            self.write_keyword("DATEDIFF");
17789            self.write("(");
17790            // First arg is unit - normalize to unquoted uppercase
17791            self.write_redshift_date_part(&func.args[0]);
17792            self.write(", ");
17793            self.generate_expression(&func.args[1])?;
17794            self.write(", ");
17795            self.generate_expression(&func.args[2])?;
17796            self.write(")");
17797            return Ok(());
17798        }
17799
17800        // Redshift: DATEADD/DATE_ADD(unit, interval, date) -> DATEADD(UNIT, interval, date)
17801        // Unit should be unquoted uppercase identifier
17802        if self.config.dialect == Some(DialectType::Redshift)
17803            && (func.name.eq_ignore_ascii_case("DATEADD")
17804                || func.name.eq_ignore_ascii_case("DATE_ADD"))
17805            && func.args.len() == 3
17806        {
17807            self.write_keyword("DATEADD");
17808            self.write("(");
17809            // First arg is unit - normalize to unquoted uppercase
17810            self.write_redshift_date_part(&func.args[0]);
17811            self.write(", ");
17812            self.generate_expression(&func.args[1])?;
17813            self.write(", ");
17814            self.generate_expression(&func.args[2])?;
17815            self.write(")");
17816            return Ok(());
17817        }
17818
17819        // UUID_STRING(args) from Snowflake -> dialect-specific UUID function.
17820        if func.name.eq_ignore_ascii_case("UUID_STRING")
17821            && !matches!(self.config.dialect, Some(DialectType::Snowflake) | None)
17822        {
17823            if matches!(
17824                self.config.dialect,
17825                Some(DialectType::Hive | DialectType::Spark | DialectType::Databricks)
17826            ) {
17827                self.write_keyword("CAST");
17828                self.write("(");
17829                self.write_keyword("UUID");
17830                self.write("() ");
17831                self.write_keyword("AS");
17832                self.write(" ");
17833                self.write_keyword("STRING");
17834                self.write(")");
17835                return Ok(());
17836            }
17837
17838            if matches!(
17839                self.config.dialect,
17840                Some(DialectType::Presto | DialectType::Trino)
17841            ) {
17842                self.write_keyword("CAST");
17843                self.write("(");
17844                self.write_keyword("UUID");
17845                self.write("() ");
17846                self.write_keyword("AS");
17847                self.write(" ");
17848                self.write_keyword("VARCHAR");
17849                self.write(")");
17850                return Ok(());
17851            }
17852
17853            if self.config.dialect == Some(DialectType::DuckDB) && func.args.len() == 2 {
17854                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(");
17855                self.generate_expression(&func.args[0])?;
17856                self.write(", '-', '')) || ENCODE(");
17857                self.generate_expression(&func.args[1])?;
17858                self.write(")), 1, 32) AS h))");
17859                return Ok(());
17860            }
17861
17862            let func_name = match self.config.dialect {
17863                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
17864                Some(DialectType::BigQuery) => "GENERATE_UUID",
17865                _ => "UUID",
17866            };
17867            self.write_keyword(func_name);
17868            self.write("()");
17869            return Ok(());
17870        }
17871
17872        // Snowflake: GENERATOR(val) -> GENERATOR(ROWCOUNT => val)
17873        // GENERATOR(val1, val2) -> GENERATOR(ROWCOUNT => val1, TIMELIMIT => val2)
17874        // Positional args are mapped to named parameters.
17875        if matches!(self.config.dialect, Some(DialectType::Snowflake))
17876            && func.name.eq_ignore_ascii_case("GENERATOR")
17877        {
17878            let has_positional_args =
17879                !func.args.is_empty() && !matches!(&func.args[0], Expression::NamedArgument(_));
17880            if has_positional_args {
17881                let param_names = ["ROWCOUNT", "TIMELIMIT"];
17882                self.write_keyword("GENERATOR");
17883                self.write("(");
17884                for (i, arg) in func.args.iter().enumerate() {
17885                    if i > 0 {
17886                        self.write(", ");
17887                    }
17888                    if i < param_names.len() {
17889                        self.write_keyword(param_names[i]);
17890                        self.write(" => ");
17891                        self.generate_expression(arg)?;
17892                    } else {
17893                        self.generate_expression(arg)?;
17894                    }
17895                }
17896                self.write(")");
17897                return Ok(());
17898            }
17899        }
17900
17901        // Redshift: DATE_TRUNC('unit', date) -> DATE_TRUNC('UNIT', date)
17902        // Unit should be quoted uppercase string
17903        if self.config.dialect == Some(DialectType::Redshift)
17904            && func.name.eq_ignore_ascii_case("DATE_TRUNC")
17905            && func.args.len() == 2
17906        {
17907            self.write_keyword("DATE_TRUNC");
17908            self.write("(");
17909            // First arg is unit - normalize to quoted uppercase
17910            self.write_redshift_date_part_quoted(&func.args[0]);
17911            self.write(", ");
17912            self.generate_expression(&func.args[1])?;
17913            self.write(")");
17914            return Ok(());
17915        }
17916
17917        // TSQL/Fabric: DATE_PART -> DATEPART (no underscore)
17918        if matches!(
17919            self.config.dialect,
17920            Some(DialectType::TSQL) | Some(DialectType::Fabric)
17921        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17922            || func.name.eq_ignore_ascii_case("DATEPART"))
17923            && func.args.len() == 2
17924        {
17925            self.write_keyword("DATEPART");
17926            self.write("(");
17927            self.generate_expression(&func.args[0])?;
17928            self.write(", ");
17929            self.generate_expression(&func.args[1])?;
17930            self.write(")");
17931            return Ok(());
17932        }
17933
17934        // PostgreSQL/Redshift: DATE_PART(part, value) -> EXTRACT(part FROM value)
17935        if matches!(
17936            self.config.dialect,
17937            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
17938        ) && (func.name.eq_ignore_ascii_case("DATE_PART")
17939            || func.name.eq_ignore_ascii_case("DATEPART"))
17940            && func.args.len() == 2
17941        {
17942            self.write_keyword("EXTRACT");
17943            self.write("(");
17944            // Extract the datetime field - if it's a string literal, strip quotes to make it a keyword
17945            match &func.args[0] {
17946                Expression::Literal(lit)
17947                    if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
17948                {
17949                    let crate::expressions::Literal::String(s) = lit.as_ref() else {
17950                        unreachable!()
17951                    };
17952                    self.write(&s.to_ascii_lowercase());
17953                }
17954                _ => self.generate_expression(&func.args[0])?,
17955            }
17956            self.write_space();
17957            self.write_keyword("FROM");
17958            self.write_space();
17959            self.generate_expression(&func.args[1])?;
17960            self.write(")");
17961            return Ok(());
17962        }
17963
17964        // PostgreSQL: DATE_ADD(date, INTERVAL '...') / DATE_SUB(...) -> infix interval arithmetic.
17965        if self.config.dialect == Some(DialectType::PostgreSQL)
17966            && matches!(
17967                func.name.to_ascii_uppercase().as_str(),
17968                "DATE_ADD" | "DATE_SUB"
17969            )
17970            && func.args.len() == 2
17971            && matches!(func.args[1], Expression::Interval(_))
17972        {
17973            self.generate_expression(&func.args[0])?;
17974            self.write_space();
17975            if func.name.eq_ignore_ascii_case("DATE_SUB") {
17976                self.write("-");
17977            } else {
17978                self.write("+");
17979            }
17980            self.write_space();
17981            self.generate_expression(&func.args[1])?;
17982            return Ok(());
17983        }
17984
17985        // Dremio: DATE_PART(part, value) -> EXTRACT(part FROM value)
17986        // Also DATE literals in Dremio should be CAST(...AS DATE)
17987        if self.config.dialect == Some(DialectType::Dremio)
17988            && (func.name.eq_ignore_ascii_case("DATE_PART")
17989                || func.name.eq_ignore_ascii_case("DATEPART"))
17990            && func.args.len() == 2
17991        {
17992            self.write_keyword("EXTRACT");
17993            self.write("(");
17994            self.generate_expression(&func.args[0])?;
17995            self.write_space();
17996            self.write_keyword("FROM");
17997            self.write_space();
17998            // For Dremio, DATE literals should become CAST('value' AS DATE)
17999            self.generate_dremio_date_expression(&func.args[1])?;
18000            self.write(")");
18001            return Ok(());
18002        }
18003
18004        // Dremio: CURRENT_DATE_UTC() -> CURRENT_DATE_UTC (no parentheses)
18005        if self.config.dialect == Some(DialectType::Dremio)
18006            && func.name.eq_ignore_ascii_case("CURRENT_DATE_UTC")
18007            && func.args.is_empty()
18008        {
18009            self.write_keyword("CURRENT_DATE_UTC");
18010            return Ok(());
18011        }
18012
18013        // Dremio: DATETYPE(year, month, day) transformation
18014        // - If all args are integer literals: DATE('YYYY-MM-DD')
18015        // - If args are expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
18016        if self.config.dialect == Some(DialectType::Dremio)
18017            && func.name.eq_ignore_ascii_case("DATETYPE")
18018            && func.args.len() == 3
18019        {
18020            // Helper function to extract integer from number literal
18021            fn get_int_literal(expr: &Expression) -> Option<i64> {
18022                if let Expression::Literal(lit) = expr {
18023                    if let crate::expressions::Literal::Number(s) = lit.as_ref() {
18024                        s.parse::<i64>().ok()
18025                    } else {
18026                        None
18027                    }
18028                } else {
18029                    None
18030                }
18031            }
18032
18033            // Check if all arguments are integer literals
18034            if let (Some(year), Some(month), Some(day)) = (
18035                get_int_literal(&func.args[0]),
18036                get_int_literal(&func.args[1]),
18037                get_int_literal(&func.args[2]),
18038            ) {
18039                // All are integer literals: DATE('YYYY-MM-DD')
18040                self.write_keyword("DATE");
18041                self.write(&format!("('{:04}-{:02}-{:02}')", year, month, day));
18042                return Ok(());
18043            }
18044
18045            // For expressions: CAST(CONCAT(x, '-', y, '-', z) AS DATE)
18046            self.write_keyword("CAST");
18047            self.write("(");
18048            self.write_keyword("CONCAT");
18049            self.write("(");
18050            self.generate_expression(&func.args[0])?;
18051            self.write(", '-', ");
18052            self.generate_expression(&func.args[1])?;
18053            self.write(", '-', ");
18054            self.generate_expression(&func.args[2])?;
18055            self.write(")");
18056            self.write_space();
18057            self.write_keyword("AS");
18058            self.write_space();
18059            self.write_keyword("DATE");
18060            self.write(")");
18061            return Ok(());
18062        }
18063
18064        // Presto/Trino: DATE_ADD('unit', interval, date) - wrap interval in CAST(...AS BIGINT)
18065        // when it's not an integer literal
18066        let is_presto_like = matches!(
18067            self.config.dialect,
18068            Some(DialectType::Presto) | Some(DialectType::Trino)
18069        );
18070        if is_presto_like && func.name.eq_ignore_ascii_case("DATE_ADD") && func.args.len() == 3 {
18071            self.write_keyword("DATE_ADD");
18072            self.write("(");
18073            // First arg: unit (pass through as-is, e.g., 'DAY')
18074            self.generate_expression(&func.args[0])?;
18075            self.write(", ");
18076            // Second arg: interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
18077            let interval = &func.args[1];
18078            let needs_cast = !self.returns_integer_type(interval);
18079            if needs_cast {
18080                self.write_keyword("CAST");
18081                self.write("(");
18082            }
18083            self.generate_expression(interval)?;
18084            if needs_cast {
18085                self.write_space();
18086                self.write_keyword("AS");
18087                self.write_space();
18088                self.write_keyword("BIGINT");
18089                self.write(")");
18090            }
18091            self.write(", ");
18092            // Third arg: date
18093            self.generate_expression(&func.args[2])?;
18094            self.write(")");
18095            return Ok(());
18096        }
18097
18098        // Use bracket syntax if the function was parsed with brackets (e.g., MAP[keys, values])
18099        let use_brackets = func.use_bracket_syntax;
18100
18101        // Special case: functions WITH ORDINALITY need special output order
18102        // Input: FUNC(args) WITH ORDINALITY
18103        // Stored as: name="FUNC WITH ORDINALITY", args=[...]
18104        // Output must be: FUNC(args) WITH ORDINALITY
18105        let has_ordinality = func.name.len() >= 16
18106            && func.name[func.name.len() - 16..].eq_ignore_ascii_case(" WITH ORDINALITY");
18107        let output_name = if has_ordinality {
18108            let base_name = &func.name[..func.name.len() - " WITH ORDINALITY".len()];
18109            self.normalize_func_name(base_name)
18110        } else {
18111            normalized_name.clone()
18112        };
18113
18114        // For qualified names (schema.function or object.method), preserve original case
18115        // because they can be case-sensitive (e.g., TSQL XML methods like .nodes(), .value())
18116        let quote_source_clickhouse_function =
18117            matches!(self.config.dialect, Some(DialectType::ClickHouse))
18118                && matches!(self.config.source_dialect, Some(DialectType::ClickHouse))
18119                && func.quoted;
18120
18121        if quote_source_clickhouse_function {
18122            self.generate_identifier(&Identifier {
18123                name: func.name.clone(),
18124                quoted: true,
18125                trailing_comments: Vec::new(),
18126                span: None,
18127            })?;
18128        } else if func.name.contains('.') && !has_ordinality {
18129            // Don't normalize qualified functions - preserve original case
18130            // If the function was quoted (e.g., BigQuery `p.d.UdF`), wrap it in backticks
18131            if func.quoted {
18132                self.write("`");
18133                self.write(&func.name);
18134                self.write("`");
18135            } else {
18136                self.write(&func.name);
18137            }
18138        } else {
18139            self.write(&output_name);
18140        }
18141
18142        // If no_parens is true and there are no args, output just the function name
18143        // Unless the target dialect requires parens for this function
18144        let force_parens = func.no_parens && func.args.is_empty() && !func.distinct && {
18145            let needs_parens = if func.name.eq_ignore_ascii_case("CURRENT_USER")
18146                || func.name.eq_ignore_ascii_case("SESSION_USER")
18147                || func.name.eq_ignore_ascii_case("SYSTEM_USER")
18148            {
18149                matches!(
18150                    self.config.dialect,
18151                    Some(DialectType::Snowflake)
18152                        | Some(DialectType::Spark)
18153                        | Some(DialectType::Databricks)
18154                        | Some(DialectType::Hive)
18155                )
18156            } else {
18157                false
18158            };
18159            !needs_parens
18160        };
18161        if force_parens {
18162            // Output trailing comments
18163            for comment in &func.trailing_comments {
18164                self.write_space();
18165                self.write_formatted_comment(comment);
18166            }
18167            return Ok(());
18168        }
18169
18170        // CUBE, ROLLUP, GROUPING SETS need a space before the parenthesis
18171        if func.name.eq_ignore_ascii_case("CUBE")
18172            || func.name.eq_ignore_ascii_case("ROLLUP")
18173            || func.name.eq_ignore_ascii_case("GROUPING SETS")
18174        {
18175            self.write(" (");
18176        } else if use_brackets {
18177            self.write("[");
18178        } else {
18179            self.write("(");
18180        }
18181        if func.distinct {
18182            self.write_keyword("DISTINCT");
18183            self.write_space();
18184        }
18185
18186        // Check if arguments should be split onto multiple lines (pretty + too wide)
18187        let compact_pretty_func = matches!(self.config.dialect, Some(DialectType::Snowflake))
18188            && (func.name.eq_ignore_ascii_case("TABLE")
18189                || func.name.eq_ignore_ascii_case("FLATTEN"));
18190        // GROUPING SETS, CUBE, ROLLUP always expand in pretty mode
18191        let is_grouping_func = func.name.eq_ignore_ascii_case("GROUPING SETS")
18192            || func.name.eq_ignore_ascii_case("CUBE")
18193            || func.name.eq_ignore_ascii_case("ROLLUP");
18194        let should_split = if self.config.pretty && !func.args.is_empty() && !compact_pretty_func {
18195            if is_grouping_func {
18196                true
18197            } else {
18198                // Pre-render arguments to check total width
18199                let mut expr_strings: Vec<String> = Vec::with_capacity(func.args.len());
18200                for arg in &func.args {
18201                    let mut temp_gen = Generator::with_arc_config(self.config.clone());
18202                    Arc::make_mut(&mut temp_gen.config).pretty = false; // Don't recurse into pretty
18203                    temp_gen.generate_expression(arg)?;
18204                    expr_strings.push(temp_gen.output);
18205                }
18206                self.too_wide(&expr_strings)
18207            }
18208        } else {
18209            false
18210        };
18211
18212        if should_split {
18213            // Split onto multiple lines
18214            self.write_newline();
18215            self.indent_level += 1;
18216            for (i, arg) in func.args.iter().enumerate() {
18217                self.write_indent();
18218                self.generate_expression(arg)?;
18219                if i + 1 < func.args.len() {
18220                    self.write(",");
18221                }
18222                self.write_newline();
18223            }
18224            self.indent_level -= 1;
18225            self.write_indent();
18226        } else {
18227            // All on one line
18228            for (i, arg) in func.args.iter().enumerate() {
18229                if i > 0 {
18230                    self.write(", ");
18231                }
18232                self.generate_expression(arg)?;
18233            }
18234        }
18235
18236        if use_brackets {
18237            self.write("]");
18238        } else {
18239            self.write(")");
18240        }
18241        // Append WITH ORDINALITY after closing paren for table-valued functions
18242        if has_ordinality {
18243            self.write_space();
18244            self.write_keyword("WITH ORDINALITY");
18245        }
18246        // Output trailing comments
18247        for comment in &func.trailing_comments {
18248            self.write_space();
18249            self.write_formatted_comment(comment);
18250        }
18251        Ok(())
18252    }
18253
18254    fn generate_function_emits(&mut self, fe: &FunctionEmits) -> Result<()> {
18255        self.generate_expression(&fe.this)?;
18256        self.write_keyword(" EMITS ");
18257        self.generate_expression(&fe.emits)?;
18258        Ok(())
18259    }
18260
18261    fn generate_aggregate_function(&mut self, func: &AggregateFunction) -> Result<()> {
18262        // Normalize function name based on dialect settings
18263        let mut normalized_name = self.normalize_func_name(&func.name);
18264
18265        // Dialect-specific name mappings for aggregate functions
18266        if func.name.eq_ignore_ascii_case("MAX_BY") || func.name.eq_ignore_ascii_case("MIN_BY") {
18267            let is_max = func.name.eq_ignore_ascii_case("MAX_BY");
18268            match self.config.dialect {
18269                Some(DialectType::ClickHouse) => {
18270                    normalized_name = if is_max {
18271                        Cow::Borrowed("argMax")
18272                    } else {
18273                        Cow::Borrowed("argMin")
18274                    };
18275                }
18276                Some(DialectType::DuckDB) => {
18277                    normalized_name = if is_max {
18278                        Cow::Borrowed("ARG_MAX")
18279                    } else {
18280                        Cow::Borrowed("ARG_MIN")
18281                    };
18282                }
18283                _ => {}
18284            }
18285        }
18286        self.write(normalized_name.as_ref());
18287        self.write("(");
18288        if func.distinct {
18289            self.write_keyword("DISTINCT");
18290            self.write_space();
18291        }
18292
18293        // Check if we need to transform multi-arg COUNT DISTINCT
18294        // When dialect doesn't support multi_arg_distinct, transform:
18295        // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
18296        let is_count = normalized_name.eq_ignore_ascii_case("COUNT");
18297        let needs_multi_arg_transform =
18298            func.distinct && is_count && func.args.len() > 1 && !self.config.multi_arg_distinct;
18299
18300        if needs_multi_arg_transform {
18301            // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
18302            self.write_keyword("CASE");
18303            for arg in &func.args {
18304                self.write_space();
18305                self.write_keyword("WHEN");
18306                self.write_space();
18307                self.generate_expression(arg)?;
18308                self.write_space();
18309                self.write_keyword("IS NULL THEN NULL");
18310            }
18311            self.write_space();
18312            self.write_keyword("ELSE");
18313            self.write(" (");
18314            for (i, arg) in func.args.iter().enumerate() {
18315                if i > 0 {
18316                    self.write(", ");
18317                }
18318                self.generate_expression(arg)?;
18319            }
18320            self.write(")");
18321            self.write_space();
18322            self.write_keyword("END");
18323        } else {
18324            for (i, arg) in func.args.iter().enumerate() {
18325                if i > 0 {
18326                    self.write(", ");
18327                }
18328                self.generate_expression(arg)?;
18329            }
18330        }
18331
18332        // IGNORE NULLS / RESPECT NULLS inside parens (for BigQuery style or when config says in_func)
18333        let clickhouse_ignore_nulls_outside =
18334            matches!(self.config.dialect, Some(DialectType::ClickHouse));
18335        if self.config.ignore_nulls_in_func
18336            && !matches!(
18337                self.config.dialect,
18338                Some(DialectType::DuckDB) | Some(DialectType::ClickHouse)
18339            )
18340        {
18341            if let Some(ignore) = func.ignore_nulls {
18342                self.write_space();
18343                if ignore {
18344                    self.write_keyword("IGNORE NULLS");
18345                } else {
18346                    self.write_keyword("RESPECT NULLS");
18347                }
18348            }
18349        }
18350
18351        // ORDER BY inside aggregate
18352        if !func.order_by.is_empty() {
18353            self.write_space();
18354            self.write_keyword("ORDER BY");
18355            self.write_space();
18356            for (i, ord) in func.order_by.iter().enumerate() {
18357                if i > 0 {
18358                    self.write(", ");
18359                }
18360                self.generate_ordered(ord)?;
18361            }
18362        }
18363
18364        // LIMIT inside aggregate
18365        if let Some(limit) = &func.limit {
18366            self.write_space();
18367            self.write_keyword("LIMIT");
18368            self.write_space();
18369            // Check if this is a Tuple representing LIMIT offset, count
18370            if let Expression::Tuple(t) = limit.as_ref() {
18371                if t.expressions.len() == 2 {
18372                    self.generate_expression(&t.expressions[0])?;
18373                    self.write(", ");
18374                    self.generate_expression(&t.expressions[1])?;
18375                } else {
18376                    self.generate_expression(limit)?;
18377                }
18378            } else {
18379                self.generate_expression(limit)?;
18380            }
18381        }
18382
18383        self.write(")");
18384
18385        // IGNORE NULLS / RESPECT NULLS outside parens (standard style)
18386        if (!self.config.ignore_nulls_in_func || clickhouse_ignore_nulls_outside)
18387            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
18388        {
18389            if let Some(ignore) = func.ignore_nulls {
18390                self.write_space();
18391                if ignore {
18392                    self.write_keyword("IGNORE NULLS");
18393                } else {
18394                    self.write_keyword("RESPECT NULLS");
18395                }
18396            }
18397        }
18398
18399        if let Some(filter) = &func.filter {
18400            self.write_space();
18401            self.write_keyword("FILTER");
18402            self.write("(");
18403            self.write_keyword("WHERE");
18404            self.write_space();
18405            self.generate_expression(filter)?;
18406            self.write(")");
18407        }
18408
18409        Ok(())
18410    }
18411
18412    fn generate_window_function(&mut self, wf: &WindowFunction) -> Result<()> {
18413        self.generate_expression(&wf.this)?;
18414
18415        // Generate KEEP clause if present (Oracle KEEP (DENSE_RANK FIRST|LAST ORDER BY ...))
18416        if let Some(keep) = &wf.keep {
18417            self.write_space();
18418            self.write_keyword("KEEP");
18419            self.write(" (");
18420            self.write_keyword("DENSE_RANK");
18421            self.write_space();
18422            if keep.first {
18423                self.write_keyword("FIRST");
18424            } else {
18425                self.write_keyword("LAST");
18426            }
18427            self.write_space();
18428            self.write_keyword("ORDER BY");
18429            self.write_space();
18430            for (i, ord) in keep.order_by.iter().enumerate() {
18431                if i > 0 {
18432                    self.write(", ");
18433                }
18434                self.generate_ordered(ord)?;
18435            }
18436            self.write(")");
18437        }
18438
18439        // Check if there's any OVER clause content
18440        let has_over = !wf.over.partition_by.is_empty()
18441            || !wf.over.order_by.is_empty()
18442            || wf.over.frame.is_some()
18443            || wf.over.window_name.is_some();
18444
18445        // Only output OVER if there's actual window specification (not just KEEP alone)
18446        if has_over {
18447            self.write_space();
18448            self.write_keyword("OVER");
18449
18450            // Check if this is just a bare named window reference (no parens needed)
18451            let has_specs = !wf.over.partition_by.is_empty()
18452                || !wf.over.order_by.is_empty()
18453                || wf.over.frame.is_some();
18454
18455            if wf.over.window_name.is_some() && !has_specs {
18456                // OVER window_name (without parentheses)
18457                self.write_space();
18458                self.write(&wf.over.window_name.as_ref().unwrap().name);
18459            } else {
18460                // OVER (...) or OVER (window_name ...)
18461                self.write(" (");
18462                self.generate_over(&wf.over)?;
18463                self.write(")");
18464            }
18465        } else if wf.keep.is_none() {
18466            // No KEEP and no OVER content, but still a WindowFunction - output empty OVER ()
18467            self.write_space();
18468            self.write_keyword("OVER");
18469            self.write(" ()");
18470        }
18471
18472        Ok(())
18473    }
18474
18475    /// Generate WITHIN GROUP clause (for ordered-set aggregate functions)
18476    fn generate_within_group(&mut self, wg: &WithinGroup) -> Result<()> {
18477        self.generate_expression(&wg.this)?;
18478        self.write_space();
18479        self.write_keyword("WITHIN GROUP");
18480        self.write(" (");
18481        self.write_keyword("ORDER BY");
18482        self.write_space();
18483        for (i, ord) in wg.order_by.iter().enumerate() {
18484            if i > 0 {
18485                self.write(", ");
18486            }
18487            self.generate_ordered(ord)?;
18488        }
18489        self.write(")");
18490        Ok(())
18491    }
18492
18493    /// Generate the contents of an OVER clause (without parentheses)
18494    fn generate_over(&mut self, over: &Over) -> Result<()> {
18495        let mut has_content = false;
18496
18497        // Named window reference
18498        if let Some(name) = &over.window_name {
18499            self.write(&name.name);
18500            has_content = true;
18501        }
18502
18503        // PARTITION BY
18504        if !over.partition_by.is_empty() {
18505            if has_content {
18506                self.write_space();
18507            }
18508            self.write_keyword("PARTITION BY");
18509            self.write_space();
18510            for (i, expr) in over.partition_by.iter().enumerate() {
18511                if i > 0 {
18512                    self.write(", ");
18513                }
18514                self.generate_expression(expr)?;
18515            }
18516            has_content = true;
18517        }
18518
18519        // ORDER BY
18520        if !over.order_by.is_empty() {
18521            if has_content {
18522                self.write_space();
18523            }
18524            self.write_keyword("ORDER BY");
18525            self.write_space();
18526            for (i, ordered) in over.order_by.iter().enumerate() {
18527                if i > 0 {
18528                    self.write(", ");
18529                }
18530                self.generate_ordered(ordered)?;
18531            }
18532            has_content = true;
18533        }
18534
18535        // Window frame
18536        if let Some(frame) = &over.frame {
18537            if has_content {
18538                self.write_space();
18539            }
18540            self.generate_window_frame(frame)?;
18541        }
18542
18543        Ok(())
18544    }
18545
18546    fn generate_window_frame(&mut self, frame: &WindowFrame) -> Result<()> {
18547        // Exasol uses lowercase for frame kind (rows/range/groups)
18548        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18549
18550        // Use preserved kind_text if available (for case preservation), unless lowercase override is active
18551        if !lowercase_frame {
18552            if let Some(kind_text) = &frame.kind_text {
18553                self.write(kind_text);
18554            } else {
18555                match frame.kind {
18556                    WindowFrameKind::Rows => self.write_keyword("ROWS"),
18557                    WindowFrameKind::Range => self.write_keyword("RANGE"),
18558                    WindowFrameKind::Groups => self.write_keyword("GROUPS"),
18559                }
18560            }
18561        } else {
18562            match frame.kind {
18563                WindowFrameKind::Rows => self.write("rows"),
18564                WindowFrameKind::Range => self.write("range"),
18565                WindowFrameKind::Groups => self.write("groups"),
18566            }
18567        }
18568
18569        // Use BETWEEN format only when there's an explicit end bound,
18570        // or when normalize_window_frame_between is enabled and the start is a directional bound
18571        self.write_space();
18572        let should_normalize = self.config.normalize_window_frame_between
18573            && frame.end.is_none()
18574            && matches!(
18575                frame.start,
18576                WindowFrameBound::Preceding(_)
18577                    | WindowFrameBound::Following(_)
18578                    | WindowFrameBound::UnboundedPreceding
18579                    | WindowFrameBound::UnboundedFollowing
18580            );
18581
18582        if let Some(end) = &frame.end {
18583            // BETWEEN format: RANGE BETWEEN start AND end
18584            self.write_keyword("BETWEEN");
18585            self.write_space();
18586            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18587            self.write_space();
18588            self.write_keyword("AND");
18589            self.write_space();
18590            self.generate_window_frame_bound(end, frame.end_side_text.as_deref())?;
18591        } else if should_normalize {
18592            // Normalize single-bound to BETWEEN form: ROWS 1 PRECEDING → ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
18593            self.write_keyword("BETWEEN");
18594            self.write_space();
18595            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18596            self.write_space();
18597            self.write_keyword("AND");
18598            self.write_space();
18599            self.write_keyword("CURRENT ROW");
18600        } else {
18601            // Single bound format: RANGE CURRENT ROW
18602            self.generate_window_frame_bound(&frame.start, frame.start_side_text.as_deref())?;
18603        }
18604
18605        // EXCLUDE clause
18606        if let Some(exclude) = &frame.exclude {
18607            self.write_space();
18608            self.write_keyword("EXCLUDE");
18609            self.write_space();
18610            match exclude {
18611                WindowFrameExclude::CurrentRow => self.write_keyword("CURRENT ROW"),
18612                WindowFrameExclude::Group => self.write_keyword("GROUP"),
18613                WindowFrameExclude::Ties => self.write_keyword("TIES"),
18614                WindowFrameExclude::NoOthers => self.write_keyword("NO OTHERS"),
18615            }
18616        }
18617
18618        Ok(())
18619    }
18620
18621    fn generate_window_frame_bound(
18622        &mut self,
18623        bound: &WindowFrameBound,
18624        side_text: Option<&str>,
18625    ) -> Result<()> {
18626        // Exasol uses lowercase for preceding/following
18627        let lowercase_frame = self.config.lowercase_window_frame_keywords;
18628
18629        match bound {
18630            WindowFrameBound::CurrentRow => {
18631                self.write_keyword("CURRENT ROW");
18632            }
18633            WindowFrameBound::UnboundedPreceding => {
18634                self.write_keyword("UNBOUNDED");
18635                self.write_space();
18636                if lowercase_frame {
18637                    self.write("preceding");
18638                } else if let Some(text) = side_text {
18639                    self.write(text);
18640                } else {
18641                    self.write_keyword("PRECEDING");
18642                }
18643            }
18644            WindowFrameBound::UnboundedFollowing => {
18645                self.write_keyword("UNBOUNDED");
18646                self.write_space();
18647                if lowercase_frame {
18648                    self.write("following");
18649                } else if let Some(text) = side_text {
18650                    self.write(text);
18651                } else {
18652                    self.write_keyword("FOLLOWING");
18653                }
18654            }
18655            WindowFrameBound::Preceding(expr) => {
18656                self.generate_expression(expr)?;
18657                self.write_space();
18658                if lowercase_frame {
18659                    self.write("preceding");
18660                } else if let Some(text) = side_text {
18661                    self.write(text);
18662                } else {
18663                    self.write_keyword("PRECEDING");
18664                }
18665            }
18666            WindowFrameBound::Following(expr) => {
18667                self.generate_expression(expr)?;
18668                self.write_space();
18669                if lowercase_frame {
18670                    self.write("following");
18671                } else if let Some(text) = side_text {
18672                    self.write(text);
18673                } else {
18674                    self.write_keyword("FOLLOWING");
18675                }
18676            }
18677            WindowFrameBound::BarePreceding => {
18678                if lowercase_frame {
18679                    self.write("preceding");
18680                } else if let Some(text) = side_text {
18681                    self.write(text);
18682                } else {
18683                    self.write_keyword("PRECEDING");
18684                }
18685            }
18686            WindowFrameBound::BareFollowing => {
18687                if lowercase_frame {
18688                    self.write("following");
18689                } else if let Some(text) = side_text {
18690                    self.write(text);
18691                } else {
18692                    self.write_keyword("FOLLOWING");
18693                }
18694            }
18695            WindowFrameBound::Value(expr) => {
18696                // Bare numeric bound without PRECEDING/FOLLOWING
18697                self.generate_expression(expr)?;
18698            }
18699        }
18700        Ok(())
18701    }
18702
18703    fn generate_interval(&mut self, interval: &Interval) -> Result<()> {
18704        // For Oracle with ExprSpan: only output INTERVAL if `this` is a literal
18705        // (e.g., `(expr) DAY(9) TO SECOND(3)` should NOT have INTERVAL prefix)
18706        let skip_interval_keyword = matches!(self.config.dialect, Some(DialectType::Oracle))
18707            && matches!(&interval.unit, Some(IntervalUnitSpec::ExprSpan(_)))
18708            && !matches!(&interval.this, Some(Expression::Literal(_)));
18709
18710        // SINGLE_STRING_INTERVAL: combine value and unit into a single quoted string
18711        // e.g., INTERVAL '1' DAY -> INTERVAL '1 DAY'
18712        if self.config.single_string_interval {
18713            if let (
18714                Some(Expression::Literal(lit)),
18715                Some(IntervalUnitSpec::Simple {
18716                    ref unit,
18717                    ref use_plural,
18718                }),
18719            ) = (&interval.this, &interval.unit)
18720            {
18721                if let Literal::String(ref val) = lit.as_ref() {
18722                    self.write_keyword("INTERVAL");
18723                    self.write_space();
18724                    let effective_plural = *use_plural && self.config.interval_allows_plural_form;
18725                    let unit_str = self.interval_unit_str(unit, effective_plural);
18726                    self.write("'");
18727                    self.write(val);
18728                    self.write(" ");
18729                    self.write(&unit_str);
18730                    self.write("'");
18731                    return Ok(());
18732                }
18733            }
18734        }
18735
18736        if !skip_interval_keyword {
18737            self.write_keyword("INTERVAL");
18738        }
18739
18740        // Generate value if present
18741        if let Some(ref value) = interval.this {
18742            if !skip_interval_keyword {
18743                self.write_space();
18744            }
18745            // If the value is a complex expression (not a literal/column/function call)
18746            // and there's a unit, wrap it in parentheses
18747            // e.g., INTERVAL (2 * 2) MONTH, INTERVAL (DAYOFMONTH(dt) - 1) DAY
18748            let needs_parens = interval.unit.is_some()
18749                && matches!(
18750                    value,
18751                    Expression::Add(_)
18752                        | Expression::Sub(_)
18753                        | Expression::Mul(_)
18754                        | Expression::Div(_)
18755                        | Expression::Mod(_)
18756                        | Expression::BitwiseAnd(_)
18757                        | Expression::BitwiseOr(_)
18758                        | Expression::BitwiseXor(_)
18759                );
18760            if needs_parens {
18761                self.write("(");
18762            }
18763            self.generate_expression(value)?;
18764            if needs_parens {
18765                self.write(")");
18766            }
18767        }
18768
18769        // Generate unit if present
18770        if let Some(ref unit_spec) = interval.unit {
18771            self.write_space();
18772            self.write_interval_unit_spec(unit_spec)?;
18773        }
18774
18775        Ok(())
18776    }
18777
18778    /// Return the string representation of an interval unit
18779    fn interval_unit_str(&self, unit: &IntervalUnit, use_plural: bool) -> &'static str {
18780        match (unit, use_plural) {
18781            (IntervalUnit::Year, false) => "YEAR",
18782            (IntervalUnit::Year, true) => "YEARS",
18783            (IntervalUnit::Quarter, false) => "QUARTER",
18784            (IntervalUnit::Quarter, true) => "QUARTERS",
18785            (IntervalUnit::Month, false) => "MONTH",
18786            (IntervalUnit::Month, true) => "MONTHS",
18787            (IntervalUnit::Week, false) => "WEEK",
18788            (IntervalUnit::Week, true) => "WEEKS",
18789            (IntervalUnit::Day, false) => "DAY",
18790            (IntervalUnit::Day, true) => "DAYS",
18791            (IntervalUnit::Hour, false) => "HOUR",
18792            (IntervalUnit::Hour, true) => "HOURS",
18793            (IntervalUnit::Minute, false) => "MINUTE",
18794            (IntervalUnit::Minute, true) => "MINUTES",
18795            (IntervalUnit::Second, false) => "SECOND",
18796            (IntervalUnit::Second, true) => "SECONDS",
18797            (IntervalUnit::Millisecond, false) => "MILLISECOND",
18798            (IntervalUnit::Millisecond, true) => "MILLISECONDS",
18799            (IntervalUnit::Microsecond, false) => "MICROSECOND",
18800            (IntervalUnit::Microsecond, true) => "MICROSECONDS",
18801            (IntervalUnit::Nanosecond, false) => "NANOSECOND",
18802            (IntervalUnit::Nanosecond, true) => "NANOSECONDS",
18803        }
18804    }
18805
18806    fn write_interval_unit_spec(&mut self, unit_spec: &IntervalUnitSpec) -> Result<()> {
18807        match unit_spec {
18808            IntervalUnitSpec::Simple { unit, use_plural } => {
18809                // If dialect doesn't allow plural forms, force singular
18810                let effective_plural = *use_plural && self.config.interval_allows_plural_form;
18811                self.write_simple_interval_unit(unit, effective_plural);
18812            }
18813            IntervalUnitSpec::Span(span) => {
18814                self.write_simple_interval_unit(&span.this, false);
18815                self.write_space();
18816                self.write_keyword("TO");
18817                self.write_space();
18818                self.write_simple_interval_unit(&span.expression, false);
18819            }
18820            IntervalUnitSpec::ExprSpan(span) => {
18821                // Expression-based interval span (e.g., DAY(9) TO SECOND(3))
18822                self.generate_expression(&span.this)?;
18823                self.write_space();
18824                self.write_keyword("TO");
18825                self.write_space();
18826                self.generate_expression(&span.expression)?;
18827            }
18828            IntervalUnitSpec::Expr(expr) => {
18829                self.generate_expression(expr)?;
18830            }
18831        }
18832        Ok(())
18833    }
18834
18835    fn write_simple_interval_unit(&mut self, unit: &IntervalUnit, use_plural: bool) {
18836        // Output interval unit, respecting plural preference
18837        match (unit, use_plural) {
18838            (IntervalUnit::Year, false) => self.write_keyword("YEAR"),
18839            (IntervalUnit::Year, true) => self.write_keyword("YEARS"),
18840            (IntervalUnit::Quarter, false) => self.write_keyword("QUARTER"),
18841            (IntervalUnit::Quarter, true) => self.write_keyword("QUARTERS"),
18842            (IntervalUnit::Month, false) => self.write_keyword("MONTH"),
18843            (IntervalUnit::Month, true) => self.write_keyword("MONTHS"),
18844            (IntervalUnit::Week, false) => self.write_keyword("WEEK"),
18845            (IntervalUnit::Week, true) => self.write_keyword("WEEKS"),
18846            (IntervalUnit::Day, false) => self.write_keyword("DAY"),
18847            (IntervalUnit::Day, true) => self.write_keyword("DAYS"),
18848            (IntervalUnit::Hour, false) => self.write_keyword("HOUR"),
18849            (IntervalUnit::Hour, true) => self.write_keyword("HOURS"),
18850            (IntervalUnit::Minute, false) => self.write_keyword("MINUTE"),
18851            (IntervalUnit::Minute, true) => self.write_keyword("MINUTES"),
18852            (IntervalUnit::Second, false) => self.write_keyword("SECOND"),
18853            (IntervalUnit::Second, true) => self.write_keyword("SECONDS"),
18854            (IntervalUnit::Millisecond, false) => self.write_keyword("MILLISECOND"),
18855            (IntervalUnit::Millisecond, true) => self.write_keyword("MILLISECONDS"),
18856            (IntervalUnit::Microsecond, false) => self.write_keyword("MICROSECOND"),
18857            (IntervalUnit::Microsecond, true) => self.write_keyword("MICROSECONDS"),
18858            (IntervalUnit::Nanosecond, false) => self.write_keyword("NANOSECOND"),
18859            (IntervalUnit::Nanosecond, true) => self.write_keyword("NANOSECONDS"),
18860        }
18861    }
18862
18863    /// Normalize a date part expression to unquoted uppercase for Redshift DATEDIFF/DATEADD
18864    /// Converts: 'day', 'days', day, days, DAY -> DAY (unquoted)
18865    fn write_redshift_date_part(&mut self, expr: &Expression) {
18866        let part_str = self.extract_date_part_string(expr);
18867        if let Some(part) = part_str {
18868            let normalized = self.normalize_date_part(&part);
18869            self.write_keyword(&normalized);
18870        } else {
18871            // If we can't extract a date part string, fall back to generating the expression
18872            let _ = self.generate_expression(expr);
18873        }
18874    }
18875
18876    /// Normalize a date part expression to quoted uppercase for Redshift DATE_TRUNC
18877    /// Converts: 'day', day, DAY -> 'DAY' (quoted)
18878    fn write_redshift_date_part_quoted(&mut self, expr: &Expression) {
18879        let part_str = self.extract_date_part_string(expr);
18880        if let Some(part) = part_str {
18881            let normalized = self.normalize_date_part(&part);
18882            self.write("'");
18883            self.write(&normalized);
18884            self.write("'");
18885        } else {
18886            // If we can't extract a date part string, fall back to generating the expression
18887            let _ = self.generate_expression(expr);
18888        }
18889    }
18890
18891    /// Extract date part string from expression (handles string literals and identifiers)
18892    fn extract_date_part_string(&self, expr: &Expression) -> Option<String> {
18893        match expr {
18894            Expression::Literal(lit)
18895                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
18896            {
18897                let crate::expressions::Literal::String(s) = lit.as_ref() else {
18898                    unreachable!()
18899                };
18900                Some(s.clone())
18901            }
18902            Expression::Identifier(id) => Some(id.name.clone()),
18903            Expression::Var(v) => Some(v.this.clone()),
18904            Expression::Column(col) if col.table.is_none() => {
18905                // Simple column reference without table prefix, treat as identifier
18906                Some(col.name.name.clone())
18907            }
18908            _ => None,
18909        }
18910    }
18911
18912    /// Normalize date part to uppercase singular form
18913    /// days -> DAY, months -> MONTH, etc.
18914    fn normalize_date_part(&self, part: &str) -> String {
18915        let mut buf = [0u8; 64];
18916        let lower: &str = if part.len() <= 64 {
18917            for (i, b) in part.bytes().enumerate() {
18918                buf[i] = b.to_ascii_lowercase();
18919            }
18920            std::str::from_utf8(&buf[..part.len()]).unwrap_or(part)
18921        } else {
18922            return part.to_ascii_uppercase();
18923        };
18924        match lower {
18925            "day" | "days" | "d" => "DAY".to_string(),
18926            "month" | "months" | "mon" | "mons" | "mm" => "MONTH".to_string(),
18927            "year" | "years" | "y" | "yy" | "yyyy" => "YEAR".to_string(),
18928            "week" | "weeks" | "w" | "wk" => "WEEK".to_string(),
18929            "hour" | "hours" | "h" | "hh" => "HOUR".to_string(),
18930            "minute" | "minutes" | "m" | "mi" | "n" => "MINUTE".to_string(),
18931            "second" | "seconds" | "s" | "ss" => "SECOND".to_string(),
18932            "millisecond" | "milliseconds" | "ms" => "MILLISECOND".to_string(),
18933            "microsecond" | "microseconds" | "us" => "MICROSECOND".to_string(),
18934            "quarter" | "quarters" | "q" | "qq" => "QUARTER".to_string(),
18935            _ => part.to_ascii_uppercase(),
18936        }
18937    }
18938
18939    fn write_datetime_field(&mut self, field: &DateTimeField) {
18940        match field {
18941            DateTimeField::Year => self.write_keyword("YEAR"),
18942            DateTimeField::Month => self.write_keyword("MONTH"),
18943            DateTimeField::Day => self.write_keyword("DAY"),
18944            DateTimeField::Hour => self.write_keyword("HOUR"),
18945            DateTimeField::Minute => self.write_keyword("MINUTE"),
18946            DateTimeField::Second => self.write_keyword("SECOND"),
18947            DateTimeField::Millisecond => self.write_keyword("MILLISECOND"),
18948            DateTimeField::Microsecond => self.write_keyword("MICROSECOND"),
18949            DateTimeField::DayOfWeek => {
18950                let name = match self.config.dialect {
18951                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFWEEK",
18952                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => "WEEKDAY",
18953                    _ => "DOW",
18954                };
18955                self.write_keyword(name);
18956            }
18957            DateTimeField::DayOfYear => {
18958                let name = match self.config.dialect {
18959                    Some(DialectType::DuckDB) | Some(DialectType::Snowflake) => "DAYOFYEAR",
18960                    _ => "DOY",
18961                };
18962                self.write_keyword(name);
18963            }
18964            DateTimeField::Week => self.write_keyword("WEEK"),
18965            DateTimeField::WeekWithModifier(modifier) => {
18966                self.write_keyword("WEEK");
18967                self.write("(");
18968                self.write(modifier);
18969                self.write(")");
18970            }
18971            DateTimeField::Quarter => self.write_keyword("QUARTER"),
18972            DateTimeField::Epoch => self.write_keyword("EPOCH"),
18973            DateTimeField::Timezone => self.write_keyword("TIMEZONE"),
18974            DateTimeField::TimezoneHour => self.write_keyword("TIMEZONE_HOUR"),
18975            DateTimeField::TimezoneMinute => self.write_keyword("TIMEZONE_MINUTE"),
18976            DateTimeField::Date => self.write_keyword("DATE"),
18977            DateTimeField::Time => self.write_keyword("TIME"),
18978            DateTimeField::Custom(name) => self.write(name),
18979        }
18980    }
18981
18982    /// Write datetime field in lowercase (for Spark/Hive/Databricks)
18983    fn write_datetime_field_lower(&mut self, field: &DateTimeField) {
18984        match field {
18985            DateTimeField::Year => self.write("year"),
18986            DateTimeField::Month => self.write("month"),
18987            DateTimeField::Day => self.write("day"),
18988            DateTimeField::Hour => self.write("hour"),
18989            DateTimeField::Minute => self.write("minute"),
18990            DateTimeField::Second => self.write("second"),
18991            DateTimeField::Millisecond => self.write("millisecond"),
18992            DateTimeField::Microsecond => self.write("microsecond"),
18993            DateTimeField::DayOfWeek => self.write("dow"),
18994            DateTimeField::DayOfYear => self.write("doy"),
18995            DateTimeField::Week => self.write("week"),
18996            DateTimeField::WeekWithModifier(modifier) => {
18997                self.write("week(");
18998                self.write(modifier);
18999                self.write(")");
19000            }
19001            DateTimeField::Quarter => self.write("quarter"),
19002            DateTimeField::Epoch => self.write("epoch"),
19003            DateTimeField::Timezone => self.write("timezone"),
19004            DateTimeField::TimezoneHour => self.write("timezone_hour"),
19005            DateTimeField::TimezoneMinute => self.write("timezone_minute"),
19006            DateTimeField::Date => self.write("date"),
19007            DateTimeField::Time => self.write("time"),
19008            DateTimeField::Custom(name) => self.write(name),
19009        }
19010    }
19011
19012    // Helper function generators
19013
19014    fn generate_simple_func(&mut self, name: &str, arg: &Expression) -> Result<()> {
19015        self.write_keyword(name);
19016        self.write("(");
19017        self.generate_expression(arg)?;
19018        self.write(")");
19019        Ok(())
19020    }
19021
19022    /// Generate a unary function, using the original name if available for round-trip preservation
19023    fn generate_unary_func(
19024        &mut self,
19025        default_name: &str,
19026        f: &crate::expressions::UnaryFunc,
19027    ) -> Result<()> {
19028        let name = f.original_name.as_deref().unwrap_or(default_name);
19029        self.write_keyword(name);
19030        self.write("(");
19031        self.generate_expression(&f.this)?;
19032        self.write(")");
19033        Ok(())
19034    }
19035
19036    /// Generate SQRT/CBRT - always use function form (matches Python SQLGlot normalization)
19037    fn generate_sqrt_cbrt(
19038        &mut self,
19039        f: &crate::expressions::UnaryFunc,
19040        func_name: &str,
19041        _op: &str,
19042    ) -> Result<()> {
19043        // Python SQLGlot normalizes |/ and ||/ to SQRT() and CBRT()
19044        // Always use function syntax for consistency
19045        self.write_keyword(func_name);
19046        self.write("(");
19047        self.generate_expression(&f.this)?;
19048        self.write(")");
19049        Ok(())
19050    }
19051
19052    fn generate_binary_func(
19053        &mut self,
19054        name: &str,
19055        arg1: &Expression,
19056        arg2: &Expression,
19057    ) -> Result<()> {
19058        self.write_keyword(name);
19059        self.write("(");
19060        self.generate_expression(arg1)?;
19061        self.write(", ");
19062        self.generate_expression(arg2)?;
19063        self.write(")");
19064        Ok(())
19065    }
19066
19067    /// Generate CHAR/CHR function with optional USING charset
19068    /// e.g., CHAR(77, 77.3, '77.3' USING utf8mb4)
19069    /// e.g., CHR(187 USING NCHAR_CS) -- Oracle
19070    fn generate_char_func(&mut self, f: &crate::expressions::CharFunc) -> Result<()> {
19071        // Use stored name if available, otherwise default to CHAR
19072        let func_name = f.name.as_deref().unwrap_or("CHAR");
19073        self.write_keyword(func_name);
19074        self.write("(");
19075        for (i, arg) in f.args.iter().enumerate() {
19076            if i > 0 {
19077                self.write(", ");
19078            }
19079            self.generate_expression(arg)?;
19080        }
19081        if let Some(ref charset) = f.charset {
19082            self.write(" ");
19083            self.write_keyword("USING");
19084            self.write(" ");
19085            self.write(charset);
19086        }
19087        self.write(")");
19088        Ok(())
19089    }
19090
19091    fn generate_power(&mut self, f: &BinaryFunc) -> Result<()> {
19092        use crate::dialects::DialectType;
19093
19094        match self.config.dialect {
19095            Some(DialectType::Teradata) => {
19096                // Teradata uses ** operator for exponentiation
19097                self.generate_expression(&f.this)?;
19098                self.write(" ** ");
19099                self.generate_expression(&f.expression)?;
19100                Ok(())
19101            }
19102            _ => {
19103                // Other dialects use POWER function
19104                self.generate_binary_func("POWER", &f.this, &f.expression)
19105            }
19106        }
19107    }
19108
19109    fn generate_vararg_func(&mut self, name: &str, args: &[Expression]) -> Result<()> {
19110        self.write_func_name(name);
19111        self.write("(");
19112        for (i, arg) in args.iter().enumerate() {
19113            if i > 0 {
19114                self.write(", ");
19115            }
19116            self.generate_expression(arg)?;
19117        }
19118        self.write(")");
19119        Ok(())
19120    }
19121
19122    // String function generators
19123
19124    fn generate_concat_ws(&mut self, f: &ConcatWs) -> Result<()> {
19125        self.write_keyword("CONCAT_WS");
19126        self.write("(");
19127        self.generate_expression(&f.separator)?;
19128        for expr in &f.expressions {
19129            self.write(", ");
19130            self.generate_expression(expr)?;
19131        }
19132        self.write(")");
19133        Ok(())
19134    }
19135
19136    fn collect_concat_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
19137        if let Expression::Concat(op) = expr {
19138            Self::collect_concat_operands(&op.left, out);
19139            Self::collect_concat_operands(&op.right, out);
19140        } else {
19141            out.push(expr);
19142        }
19143    }
19144
19145    fn generate_mysql_concat_from_concat(&mut self, op: &BinaryOp) -> Result<()> {
19146        let mut operands = Vec::new();
19147        Self::collect_concat_operands(&op.left, &mut operands);
19148        Self::collect_concat_operands(&op.right, &mut operands);
19149
19150        self.write_keyword("CONCAT");
19151        self.write("(");
19152        for (i, operand) in operands.iter().enumerate() {
19153            if i > 0 {
19154                self.write(", ");
19155            }
19156            self.generate_expression(operand)?;
19157        }
19158        self.write(")");
19159        Ok(())
19160    }
19161
19162    fn collect_dpipe_operands<'a>(expr: &'a Expression, out: &mut Vec<&'a Expression>) {
19163        if let Expression::DPipe(dpipe) = expr {
19164            Self::collect_dpipe_operands(&dpipe.this, out);
19165            Self::collect_dpipe_operands(&dpipe.expression, out);
19166        } else {
19167            out.push(expr);
19168        }
19169    }
19170
19171    fn generate_mysql_concat_from_dpipe(&mut self, e: &DPipe) -> Result<()> {
19172        let mut operands = Vec::new();
19173        Self::collect_dpipe_operands(&e.this, &mut operands);
19174        Self::collect_dpipe_operands(&e.expression, &mut operands);
19175
19176        self.write_keyword("CONCAT");
19177        self.write("(");
19178        for (i, operand) in operands.iter().enumerate() {
19179            if i > 0 {
19180                self.write(", ");
19181            }
19182            self.generate_expression(operand)?;
19183        }
19184        self.write(")");
19185        Ok(())
19186    }
19187
19188    fn generate_substring(&mut self, f: &SubstringFunc) -> Result<()> {
19189        // Oracle and Presto-family dialects use SUBSTR; most others use SUBSTRING
19190        let use_substr = matches!(
19191            self.config.dialect,
19192            Some(
19193                DialectType::Oracle
19194                    | DialectType::Presto
19195                    | DialectType::Trino
19196                    | DialectType::Athena
19197            )
19198        );
19199        if use_substr {
19200            self.write_keyword("SUBSTR");
19201        } else {
19202            self.write_keyword("SUBSTRING");
19203        }
19204        self.write("(");
19205        self.generate_expression(&f.this)?;
19206        // PostgreSQL always uses FROM/FOR syntax
19207        let force_from_for = matches!(self.config.dialect, Some(DialectType::PostgreSQL));
19208        // Spark/Hive/TSQL/Fabric use comma syntax, not FROM/FOR syntax
19209        let use_comma_syntax = matches!(
19210            self.config.dialect,
19211            Some(DialectType::Spark)
19212                | Some(DialectType::Hive)
19213                | Some(DialectType::Databricks)
19214                | Some(DialectType::TSQL)
19215                | Some(DialectType::Fabric)
19216        );
19217        if (f.from_for_syntax || force_from_for) && !use_comma_syntax {
19218            // SQL standard syntax: SUBSTRING(str FROM pos FOR len)
19219            self.write_space();
19220            self.write_keyword("FROM");
19221            self.write_space();
19222            self.generate_expression(&f.start)?;
19223            if let Some(length) = &f.length {
19224                self.write_space();
19225                self.write_keyword("FOR");
19226                self.write_space();
19227                self.generate_expression(length)?;
19228            }
19229        } else {
19230            // Comma-separated syntax: SUBSTRING(str, pos, len) or SUBSTR(str, pos, len)
19231            self.write(", ");
19232            self.generate_expression(&f.start)?;
19233            if let Some(length) = &f.length {
19234                self.write(", ");
19235                self.generate_expression(length)?;
19236            }
19237        }
19238        self.write(")");
19239        Ok(())
19240    }
19241
19242    fn generate_overlay(&mut self, f: &OverlayFunc) -> Result<()> {
19243        self.write_keyword("OVERLAY");
19244        self.write("(");
19245        self.generate_expression(&f.this)?;
19246        self.write_space();
19247        self.write_keyword("PLACING");
19248        self.write_space();
19249        self.generate_expression(&f.replacement)?;
19250        self.write_space();
19251        self.write_keyword("FROM");
19252        self.write_space();
19253        self.generate_expression(&f.from)?;
19254        if let Some(length) = &f.length {
19255            self.write_space();
19256            self.write_keyword("FOR");
19257            self.write_space();
19258            self.generate_expression(length)?;
19259        }
19260        self.write(")");
19261        Ok(())
19262    }
19263
19264    fn generate_trim(&mut self, f: &TrimFunc) -> Result<()> {
19265        // Special case: TRIM(LEADING str) -> LTRIM(str), TRIM(TRAILING str) -> RTRIM(str)
19266        // when no characters are specified (PostgreSQL style)
19267        if f.position_explicit && f.characters.is_none() {
19268            match f.position {
19269                TrimPosition::Leading => {
19270                    self.write_keyword("LTRIM");
19271                    self.write("(");
19272                    self.generate_expression(&f.this)?;
19273                    self.write(")");
19274                    return Ok(());
19275                }
19276                TrimPosition::Trailing => {
19277                    self.write_keyword("RTRIM");
19278                    self.write("(");
19279                    self.generate_expression(&f.this)?;
19280                    self.write(")");
19281                    return Ok(());
19282                }
19283                TrimPosition::Both => {
19284                    // TRIM(BOTH str) -> BTRIM(str) in PostgreSQL, but TRIM(str) is more standard
19285                    // Fall through to standard TRIM handling
19286                }
19287            }
19288        }
19289
19290        self.write_keyword("TRIM");
19291        self.write("(");
19292        // When BOTH is specified without trim characters, simplify to just TRIM(str)
19293        // Force standard syntax for dialects that require it (Hive, Spark, Databricks, ClickHouse)
19294        let force_standard = f.characters.is_some()
19295            && !f.sql_standard_syntax
19296            && matches!(
19297                self.config.dialect,
19298                Some(DialectType::Hive)
19299                    | Some(DialectType::Spark)
19300                    | Some(DialectType::Databricks)
19301                    | Some(DialectType::ClickHouse)
19302            );
19303        let use_standard = (f.sql_standard_syntax || force_standard)
19304            && !(f.position_explicit
19305                && f.characters.is_none()
19306                && matches!(f.position, TrimPosition::Both));
19307        if use_standard {
19308            // SQL standard syntax: TRIM(BOTH chars FROM str)
19309            // Only output position if it was explicitly specified
19310            if f.position_explicit {
19311                match f.position {
19312                    TrimPosition::Both => self.write_keyword("BOTH"),
19313                    TrimPosition::Leading => self.write_keyword("LEADING"),
19314                    TrimPosition::Trailing => self.write_keyword("TRAILING"),
19315                }
19316                self.write_space();
19317            }
19318            if let Some(chars) = &f.characters {
19319                self.generate_expression(chars)?;
19320                self.write_space();
19321            }
19322            self.write_keyword("FROM");
19323            self.write_space();
19324            self.generate_expression(&f.this)?;
19325        } else {
19326            // Simple function syntax: TRIM(str) or TRIM(str, chars)
19327            self.generate_expression(&f.this)?;
19328            if let Some(chars) = &f.characters {
19329                self.write(", ");
19330                self.generate_expression(chars)?;
19331            }
19332        }
19333        self.write(")");
19334        Ok(())
19335    }
19336
19337    fn generate_replace(&mut self, f: &ReplaceFunc) -> Result<()> {
19338        self.write_keyword("REPLACE");
19339        self.write("(");
19340        self.generate_expression(&f.this)?;
19341        self.write(", ");
19342        self.generate_expression(&f.old)?;
19343        self.write(", ");
19344        self.generate_expression(&f.new)?;
19345        self.write(")");
19346        Ok(())
19347    }
19348
19349    fn generate_left_right(&mut self, name: &str, f: &LeftRightFunc) -> Result<()> {
19350        self.write_keyword(name);
19351        self.write("(");
19352        self.generate_expression(&f.this)?;
19353        self.write(", ");
19354        self.generate_expression(&f.length)?;
19355        self.write(")");
19356        Ok(())
19357    }
19358
19359    fn generate_repeat(&mut self, f: &RepeatFunc) -> Result<()> {
19360        self.write_keyword("REPEAT");
19361        self.write("(");
19362        self.generate_expression(&f.this)?;
19363        self.write(", ");
19364        self.generate_expression(&f.times)?;
19365        self.write(")");
19366        Ok(())
19367    }
19368
19369    fn generate_pad(&mut self, name: &str, f: &PadFunc) -> Result<()> {
19370        self.write_keyword(name);
19371        self.write("(");
19372        self.generate_expression(&f.this)?;
19373        self.write(", ");
19374        self.generate_expression(&f.length)?;
19375        if let Some(fill) = &f.fill {
19376            self.write(", ");
19377            self.generate_expression(fill)?;
19378        }
19379        self.write(")");
19380        Ok(())
19381    }
19382
19383    fn generate_split(&mut self, f: &SplitFunc) -> Result<()> {
19384        self.write_keyword("SPLIT");
19385        self.write("(");
19386        self.generate_expression(&f.this)?;
19387        self.write(", ");
19388        self.generate_expression(&f.delimiter)?;
19389        self.write(")");
19390        Ok(())
19391    }
19392
19393    fn generate_regexp_like(&mut self, f: &RegexpFunc) -> Result<()> {
19394        use crate::dialects::DialectType;
19395        // PostgreSQL uses ~ operator for regex matching
19396        if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) && f.flags.is_none() {
19397            self.generate_expression(&f.this)?;
19398            self.write(" ~ ");
19399            self.generate_expression(&f.pattern)?;
19400        } else if matches!(self.config.dialect, Some(DialectType::Exasol)) && f.flags.is_none() {
19401            // Exasol uses REGEXP_LIKE as infix binary operator
19402            self.generate_expression(&f.this)?;
19403            self.write_keyword(" REGEXP_LIKE ");
19404            self.generate_expression(&f.pattern)?;
19405        } else if matches!(
19406            self.config.dialect,
19407            Some(DialectType::SingleStore)
19408                | Some(DialectType::Spark)
19409                | Some(DialectType::Hive)
19410                | Some(DialectType::Databricks)
19411        ) && f.flags.is_none()
19412        {
19413            // SingleStore/Spark/Hive/Databricks use RLIKE infix operator
19414            self.generate_expression(&f.this)?;
19415            self.write_keyword(" RLIKE ");
19416            self.generate_expression(&f.pattern)?;
19417        } else if matches!(self.config.dialect, Some(DialectType::StarRocks)) {
19418            // StarRocks uses REGEXP function syntax
19419            self.write_keyword("REGEXP");
19420            self.write("(");
19421            self.generate_expression(&f.this)?;
19422            self.write(", ");
19423            self.generate_expression(&f.pattern)?;
19424            if let Some(flags) = &f.flags {
19425                self.write(", ");
19426                self.generate_expression(flags)?;
19427            }
19428            self.write(")");
19429        } else {
19430            self.write_keyword("REGEXP_LIKE");
19431            self.write("(");
19432            self.generate_expression(&f.this)?;
19433            self.write(", ");
19434            self.generate_expression(&f.pattern)?;
19435            if let Some(flags) = &f.flags {
19436                self.write(", ");
19437                self.generate_expression(flags)?;
19438            }
19439            self.write(")");
19440        }
19441        Ok(())
19442    }
19443
19444    fn generate_regexp_replace(&mut self, f: &RegexpReplaceFunc) -> Result<()> {
19445        self.write_keyword("REGEXP_REPLACE");
19446        self.write("(");
19447        self.generate_expression(&f.this)?;
19448        self.write(", ");
19449        self.generate_expression(&f.pattern)?;
19450        self.write(", ");
19451        self.generate_expression(&f.replacement)?;
19452        if let Some(flags) = &f.flags {
19453            self.write(", ");
19454            self.generate_expression(flags)?;
19455        }
19456        self.write(")");
19457        Ok(())
19458    }
19459
19460    fn generate_regexp_extract(&mut self, f: &RegexpExtractFunc) -> Result<()> {
19461        self.write_keyword("REGEXP_EXTRACT");
19462        self.write("(");
19463        self.generate_expression(&f.this)?;
19464        self.write(", ");
19465        self.generate_expression(&f.pattern)?;
19466        if let Some(group) = &f.group {
19467            self.write(", ");
19468            self.generate_expression(group)?;
19469        }
19470        self.write(")");
19471        Ok(())
19472    }
19473
19474    // Math function generators
19475
19476    fn generate_round(&mut self, f: &RoundFunc) -> Result<()> {
19477        self.write_keyword("ROUND");
19478        self.write("(");
19479        self.generate_expression(&f.this)?;
19480        if let Some(decimals) = &f.decimals {
19481            self.write(", ");
19482            self.generate_expression(decimals)?;
19483        }
19484        self.write(")");
19485        Ok(())
19486    }
19487
19488    fn generate_floor(&mut self, f: &FloorFunc) -> Result<()> {
19489        self.write_keyword("FLOOR");
19490        self.write("(");
19491        self.generate_expression(&f.this)?;
19492        // Handle Druid-style FLOOR(time TO unit) syntax
19493        if let Some(to) = &f.to {
19494            self.write(" ");
19495            self.write_keyword("TO");
19496            self.write(" ");
19497            self.generate_expression(to)?;
19498        } else if let Some(scale) = &f.scale {
19499            self.write(", ");
19500            self.generate_expression(scale)?;
19501        }
19502        self.write(")");
19503        Ok(())
19504    }
19505
19506    fn generate_ceil(&mut self, f: &CeilFunc) -> Result<()> {
19507        self.write_keyword("CEIL");
19508        self.write("(");
19509        self.generate_expression(&f.this)?;
19510        // Handle Druid-style CEIL(time TO unit) syntax
19511        if let Some(to) = &f.to {
19512            self.write(" ");
19513            self.write_keyword("TO");
19514            self.write(" ");
19515            self.generate_expression(to)?;
19516        } else if let Some(decimals) = &f.decimals {
19517            self.write(", ");
19518            self.generate_expression(decimals)?;
19519        }
19520        self.write(")");
19521        Ok(())
19522    }
19523
19524    fn generate_log(&mut self, f: &LogFunc) -> Result<()> {
19525        use crate::expressions::Literal;
19526
19527        if let Some(base) = &f.base {
19528            // Check for LOG_BASE_FIRST = None dialects (Presto, Trino, ClickHouse, Athena)
19529            // These dialects use LOG2()/LOG10() instead of LOG(base, value)
19530            if self.is_log_base_none() {
19531                if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "2"))
19532                {
19533                    self.write_func_name("LOG2");
19534                    self.write("(");
19535                    self.generate_expression(&f.this)?;
19536                    self.write(")");
19537                    return Ok(());
19538                } else if matches!(base, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(s) if s == "10"))
19539                {
19540                    self.write_func_name("LOG10");
19541                    self.write("(");
19542                    self.generate_expression(&f.this)?;
19543                    self.write(")");
19544                    return Ok(());
19545                }
19546                // Other bases: fall through to LOG(base, value) — best effort
19547            }
19548
19549            self.write_func_name("LOG");
19550            self.write("(");
19551            if self.is_log_value_first() {
19552                // BigQuery, TSQL, Tableau, Fabric: LOG(value, base)
19553                self.generate_expression(&f.this)?;
19554                self.write(", ");
19555                self.generate_expression(base)?;
19556            } else {
19557                // Default (PostgreSQL, etc.): LOG(base, value)
19558                self.generate_expression(base)?;
19559                self.write(", ");
19560                self.generate_expression(&f.this)?;
19561            }
19562            self.write(")");
19563        } else {
19564            // Single arg: LOG(x) — unspecified base (log base 10 in default dialect)
19565            self.write_func_name("LOG");
19566            self.write("(");
19567            self.generate_expression(&f.this)?;
19568            self.write(")");
19569        }
19570        Ok(())
19571    }
19572
19573    /// Whether the target dialect uses LOG(value, base) order (value first).
19574    /// BigQuery, TSQL, Tableau, Fabric use LOG(value, base).
19575    fn is_log_value_first(&self) -> bool {
19576        use crate::dialects::DialectType;
19577        matches!(
19578            self.config.dialect,
19579            Some(DialectType::BigQuery)
19580                | Some(DialectType::TSQL)
19581                | Some(DialectType::Tableau)
19582                | Some(DialectType::Fabric)
19583        )
19584    }
19585
19586    /// Whether the target dialect has LOG_BASE_FIRST = None (uses LOG2/LOG10 instead).
19587    /// Presto, Trino, ClickHouse, Athena.
19588    fn is_log_base_none(&self) -> bool {
19589        use crate::dialects::DialectType;
19590        matches!(
19591            self.config.dialect,
19592            Some(DialectType::Presto)
19593                | Some(DialectType::Trino)
19594                | Some(DialectType::ClickHouse)
19595                | Some(DialectType::Athena)
19596        )
19597    }
19598
19599    // Date/time function generators
19600
19601    fn generate_current_time(&mut self, f: &CurrentTime) -> Result<()> {
19602        self.write_keyword("CURRENT_TIME");
19603        if let Some(precision) = f.precision {
19604            self.write(&format!("({})", precision));
19605        } else if matches!(
19606            self.config.dialect,
19607            Some(crate::dialects::DialectType::MySQL)
19608                | Some(crate::dialects::DialectType::SingleStore)
19609                | Some(crate::dialects::DialectType::TiDB)
19610        ) {
19611            self.write("()");
19612        }
19613        Ok(())
19614    }
19615
19616    fn generate_current_timestamp(&mut self, f: &CurrentTimestamp) -> Result<()> {
19617        use crate::dialects::DialectType;
19618
19619        // Oracle/Redshift SYSDATE handling
19620        if f.sysdate {
19621            match self.config.dialect {
19622                Some(DialectType::Oracle) | Some(DialectType::Redshift) => {
19623                    self.write_keyword("SYSDATE");
19624                    return Ok(());
19625                }
19626                Some(DialectType::Snowflake) => {
19627                    // Snowflake uses SYSDATE() function
19628                    self.write_keyword("SYSDATE");
19629                    self.write("()");
19630                    return Ok(());
19631                }
19632                _ => {
19633                    // Other dialects use CURRENT_TIMESTAMP for SYSDATE
19634                }
19635            }
19636        }
19637
19638        self.write_keyword("CURRENT_TIMESTAMP");
19639        // MySQL, Spark, Hive always use CURRENT_TIMESTAMP() with parentheses
19640        if let Some(precision) = f.precision {
19641            self.write(&format!("({})", precision));
19642        } else if matches!(
19643            self.config.dialect,
19644            Some(crate::dialects::DialectType::MySQL)
19645                | Some(crate::dialects::DialectType::SingleStore)
19646                | Some(crate::dialects::DialectType::TiDB)
19647                | Some(crate::dialects::DialectType::Spark)
19648                | Some(crate::dialects::DialectType::Hive)
19649                | Some(crate::dialects::DialectType::Databricks)
19650                | Some(crate::dialects::DialectType::ClickHouse)
19651                | Some(crate::dialects::DialectType::BigQuery)
19652                | Some(crate::dialects::DialectType::Snowflake)
19653                | Some(crate::dialects::DialectType::Exasol)
19654        ) {
19655            self.write("()");
19656        }
19657        Ok(())
19658    }
19659
19660    fn generate_at_time_zone(&mut self, f: &AtTimeZone) -> Result<()> {
19661        // Exasol uses CONVERT_TZ(timestamp, 'UTC', zone) instead of AT TIME ZONE
19662        if self.config.dialect == Some(DialectType::Exasol) {
19663            self.write_keyword("CONVERT_TZ");
19664            self.write("(");
19665            self.generate_expression(&f.this)?;
19666            self.write(", 'UTC', ");
19667            self.generate_expression(&f.zone)?;
19668            self.write(")");
19669            return Ok(());
19670        }
19671
19672        self.generate_expression(&f.this)?;
19673        self.write_space();
19674        self.write_keyword("AT TIME ZONE");
19675        self.write_space();
19676        self.generate_expression(&f.zone)?;
19677        Ok(())
19678    }
19679
19680    fn generate_date_add(&mut self, f: &DateAddFunc, name: &str) -> Result<()> {
19681        use crate::dialects::DialectType;
19682
19683        // Presto/Trino use DATE_ADD('unit', interval, date) format
19684        // with the interval cast to BIGINT when needed
19685        let is_presto_like = matches!(
19686            self.config.dialect,
19687            Some(DialectType::Presto) | Some(DialectType::Trino)
19688        );
19689
19690        if is_presto_like {
19691            self.write_keyword(name);
19692            self.write("(");
19693            // Unit as string literal
19694            self.write("'");
19695            self.write_simple_interval_unit(&f.unit, false);
19696            self.write("'");
19697            self.write(", ");
19698            // Interval - wrap in CAST(...AS BIGINT) if it doesn't return integer type
19699            let needs_cast = !self.returns_integer_type(&f.interval);
19700            if needs_cast {
19701                self.write_keyword("CAST");
19702                self.write("(");
19703            }
19704            self.generate_expression(&f.interval)?;
19705            if needs_cast {
19706                self.write_space();
19707                self.write_keyword("AS");
19708                self.write_space();
19709                self.write_keyword("BIGINT");
19710                self.write(")");
19711            }
19712            self.write(", ");
19713            self.generate_expression(&f.this)?;
19714            self.write(")");
19715        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
19716            self.generate_expression(&f.this)?;
19717            self.write_space();
19718            if name.eq_ignore_ascii_case("DATE_SUB") {
19719                self.write("-");
19720            } else {
19721                self.write("+");
19722            }
19723            self.write_space();
19724            self.write_keyword("INTERVAL");
19725            self.write_space();
19726            self.write("'");
19727            let mut interval_gen = Generator::with_arc_config(self.config.clone());
19728            let interval_sql = interval_gen.generate(&f.interval)?;
19729            self.write(&interval_sql);
19730            self.write(" ");
19731            self.write_simple_interval_unit(&f.unit, false);
19732            self.write("'");
19733        } else {
19734            self.write_keyword(name);
19735            self.write("(");
19736            self.generate_expression(&f.this)?;
19737            self.write(", ");
19738            self.write_keyword("INTERVAL");
19739            self.write_space();
19740            self.generate_expression(&f.interval)?;
19741            self.write_space();
19742            self.write_simple_interval_unit(&f.unit, false); // Use singular form for DATEADD
19743            self.write(")");
19744        }
19745        Ok(())
19746    }
19747
19748    /// Check if an expression returns an integer type (doesn't need cast to BIGINT in Presto DATE_ADD)
19749    /// This is a heuristic to avoid full type inference
19750    fn returns_integer_type(&self, expr: &Expression) -> bool {
19751        use crate::expressions::{DataType, Literal};
19752        match expr {
19753            // Integer literals (no decimal point)
19754            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
19755                let Literal::Number(n) = lit.as_ref() else {
19756                    unreachable!()
19757                };
19758                !n.contains('.')
19759            }
19760
19761            // FLOOR(x) returns integer if x is integer
19762            Expression::Floor(f) => self.returns_integer_type(&f.this),
19763
19764            // ROUND(x) returns integer if x is integer
19765            Expression::Round(f) => {
19766                // Only if no decimals arg or it's returning an integer
19767                f.decimals.is_none() && self.returns_integer_type(&f.this)
19768            }
19769
19770            // SIGN returns integer if input is integer
19771            Expression::Sign(f) => self.returns_integer_type(&f.this),
19772
19773            // ABS returns the same type as input
19774            Expression::Abs(f) => self.returns_integer_type(&f.this),
19775
19776            // Arithmetic operations on integers return integers
19777            Expression::Mul(op) => {
19778                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19779            }
19780            Expression::Add(op) => {
19781                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19782            }
19783            Expression::Sub(op) => {
19784                self.returns_integer_type(&op.left) && self.returns_integer_type(&op.right)
19785            }
19786            Expression::Mod(op) => self.returns_integer_type(&op.left),
19787
19788            // CAST(x AS BIGINT/INT/INTEGER/SMALLINT/TINYINT) returns integer
19789            Expression::Cast(c) => matches!(
19790                &c.to,
19791                DataType::BigInt { .. }
19792                    | DataType::Int { .. }
19793                    | DataType::SmallInt { .. }
19794                    | DataType::TinyInt { .. }
19795            ),
19796
19797            // Negation: -x returns integer if x is integer
19798            Expression::Neg(op) => self.returns_integer_type(&op.this),
19799
19800            // Parenthesized expression
19801            Expression::Paren(p) => self.returns_integer_type(&p.this),
19802
19803            // Column references and most expressions are assumed to need casting
19804            // since we don't have full type information
19805            _ => false,
19806        }
19807    }
19808
19809    fn generate_datediff(&mut self, f: &DateDiffFunc) -> Result<()> {
19810        self.write_keyword("DATEDIFF");
19811        self.write("(");
19812        if let Some(unit) = &f.unit {
19813            self.write_simple_interval_unit(unit, false); // Use singular form for DATEDIFF
19814            self.write(", ");
19815        }
19816        if self.config.dialect == Some(DialectType::Snowflake) {
19817            self.generate_expression(&f.expression)?;
19818            self.write(", ");
19819            self.generate_expression(&f.this)?;
19820        } else {
19821            self.generate_expression(&f.this)?;
19822            self.write(", ");
19823            self.generate_expression(&f.expression)?;
19824        }
19825        self.write(")");
19826        Ok(())
19827    }
19828
19829    fn generate_date_trunc(&mut self, f: &DateTruncFunc) -> Result<()> {
19830        if self.config.dialect == Some(DialectType::ClickHouse) {
19831            self.write("dateTrunc");
19832        } else {
19833            self.write_keyword("DATE_TRUNC");
19834        }
19835        self.write("('");
19836        self.write_datetime_field(&f.unit);
19837        self.write("', ");
19838        self.generate_expression(&f.this)?;
19839        self.write(")");
19840        Ok(())
19841    }
19842
19843    fn generate_last_day(&mut self, f: &LastDayFunc) -> Result<()> {
19844        use crate::dialects::DialectType;
19845        use crate::expressions::DateTimeField;
19846
19847        self.write_keyword("LAST_DAY");
19848        self.write("(");
19849        self.generate_expression(&f.this)?;
19850        if let Some(unit) = &f.unit {
19851            self.write(", ");
19852            // BigQuery: strip week-start modifier from WEEK(SUNDAY), WEEK(MONDAY), etc.
19853            // WEEK(SUNDAY) -> WEEK
19854            if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
19855                if let DateTimeField::WeekWithModifier(_) = unit {
19856                    self.write_keyword("WEEK");
19857                } else {
19858                    self.write_datetime_field(unit);
19859                }
19860            } else {
19861                self.write_datetime_field(unit);
19862            }
19863        }
19864        self.write(")");
19865        Ok(())
19866    }
19867
19868    fn generate_extract(&mut self, f: &ExtractFunc) -> Result<()> {
19869        // TSQL/Fabric use DATEPART(part, expr) instead of EXTRACT(part FROM expr)
19870        if matches!(
19871            self.config.dialect,
19872            Some(DialectType::TSQL) | Some(DialectType::Fabric)
19873        ) {
19874            self.write_keyword("DATEPART");
19875            self.write("(");
19876            self.write_datetime_field(&f.field);
19877            self.write(", ");
19878            self.generate_expression(&f.this)?;
19879            self.write(")");
19880            return Ok(());
19881        }
19882        self.write_keyword("EXTRACT");
19883        self.write("(");
19884        // Hive/Spark use lowercase datetime fields in EXTRACT
19885        if matches!(
19886            self.config.dialect,
19887            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
19888        ) {
19889            self.write_datetime_field_lower(&f.field);
19890        } else {
19891            self.write_datetime_field(&f.field);
19892        }
19893        self.write_space();
19894        self.write_keyword("FROM");
19895        self.write_space();
19896        self.generate_expression(&f.this)?;
19897        self.write(")");
19898        Ok(())
19899    }
19900
19901    fn generate_to_date(&mut self, f: &ToDateFunc) -> Result<()> {
19902        self.write_keyword("TO_DATE");
19903        self.write("(");
19904        self.generate_expression(&f.this)?;
19905        if let Some(format) = &f.format {
19906            self.write(", ");
19907            self.generate_expression(format)?;
19908        }
19909        self.write(")");
19910        Ok(())
19911    }
19912
19913    fn generate_to_timestamp(&mut self, f: &ToTimestampFunc) -> Result<()> {
19914        self.write_keyword("TO_TIMESTAMP");
19915        self.write("(");
19916        self.generate_expression(&f.this)?;
19917        if let Some(format) = &f.format {
19918            self.write(", ");
19919            self.generate_expression(format)?;
19920        }
19921        self.write(")");
19922        Ok(())
19923    }
19924
19925    // Control flow function generators
19926
19927    fn generate_if_func(&mut self, f: &IfFunc) -> Result<()> {
19928        use crate::dialects::DialectType;
19929
19930        // Generic mode: normalize IF to CASE WHEN
19931        if self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic) {
19932            self.write_keyword("CASE WHEN");
19933            self.write_space();
19934            self.generate_expression(&f.condition)?;
19935            self.write_space();
19936            self.write_keyword("THEN");
19937            self.write_space();
19938            self.generate_expression(&f.true_value)?;
19939            if let Some(false_val) = &f.false_value {
19940                self.write_space();
19941                self.write_keyword("ELSE");
19942                self.write_space();
19943                self.generate_expression(false_val)?;
19944            }
19945            self.write_space();
19946            self.write_keyword("END");
19947            return Ok(());
19948        }
19949
19950        // Exasol uses IF condition THEN true_value ELSE false_value ENDIF syntax
19951        if self.config.dialect == Some(DialectType::Exasol) {
19952            self.write_keyword("IF");
19953            self.write_space();
19954            self.generate_expression(&f.condition)?;
19955            self.write_space();
19956            self.write_keyword("THEN");
19957            self.write_space();
19958            self.generate_expression(&f.true_value)?;
19959            if let Some(false_val) = &f.false_value {
19960                self.write_space();
19961                self.write_keyword("ELSE");
19962                self.write_space();
19963                self.generate_expression(false_val)?;
19964            }
19965            self.write_space();
19966            self.write_keyword("ENDIF");
19967            return Ok(());
19968        }
19969
19970        // Choose function name based on target dialect
19971        let func_name = match self.config.dialect {
19972            Some(DialectType::ClickHouse) => f.original_name.as_deref().unwrap_or("IF"),
19973            Some(DialectType::Snowflake) => "IFF",
19974            Some(DialectType::SQLite) | Some(DialectType::TSQL) => "IIF",
19975            Some(DialectType::Drill) => "`IF`",
19976            _ => "IF",
19977        };
19978        self.write(func_name);
19979        self.write("(");
19980        self.generate_expression(&f.condition)?;
19981        self.write(", ");
19982        self.generate_expression(&f.true_value)?;
19983        if let Some(false_val) = &f.false_value {
19984            self.write(", ");
19985            self.generate_expression(false_val)?;
19986        }
19987        self.write(")");
19988        Ok(())
19989    }
19990
19991    fn generate_nvl2(&mut self, f: &Nvl2Func) -> Result<()> {
19992        self.write_keyword("NVL2");
19993        self.write("(");
19994        self.generate_expression(&f.this)?;
19995        self.write(", ");
19996        self.generate_expression(&f.true_value)?;
19997        self.write(", ");
19998        self.generate_expression(&f.false_value)?;
19999        self.write(")");
20000        Ok(())
20001    }
20002
20003    // Typed aggregate function generators
20004
20005    fn generate_count(&mut self, f: &CountFunc) -> Result<()> {
20006        // Use normalize_functions for COUNT to respect ClickHouse case preservation
20007        let count_name = match self.config.normalize_functions {
20008            NormalizeFunctions::Upper => "COUNT".to_string(),
20009            NormalizeFunctions::Lower => "count".to_string(),
20010            NormalizeFunctions::None => f
20011                .original_name
20012                .clone()
20013                .unwrap_or_else(|| "COUNT".to_string()),
20014        };
20015        self.write(&count_name);
20016        self.write("(");
20017        if f.distinct {
20018            self.write_keyword("DISTINCT");
20019            self.write_space();
20020        }
20021        if f.star {
20022            self.write("*");
20023        } else if let Some(ref expr) = f.this {
20024            // For COUNT(DISTINCT a, b), unwrap the Tuple to avoid extra parentheses
20025            if let Expression::Tuple(tuple) = expr {
20026                // Check if we need to transform multi-arg COUNT DISTINCT
20027                // When dialect doesn't support multi_arg_distinct, transform:
20028                // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
20029                let needs_transform =
20030                    f.distinct && tuple.expressions.len() > 1 && !self.config.multi_arg_distinct;
20031
20032                if needs_transform {
20033                    // Generate: CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
20034                    self.write_keyword("CASE");
20035                    for e in &tuple.expressions {
20036                        self.write_space();
20037                        self.write_keyword("WHEN");
20038                        self.write_space();
20039                        self.generate_expression(e)?;
20040                        self.write_space();
20041                        self.write_keyword("IS NULL THEN NULL");
20042                    }
20043                    self.write_space();
20044                    self.write_keyword("ELSE");
20045                    self.write(" (");
20046                    for (i, e) in tuple.expressions.iter().enumerate() {
20047                        if i > 0 {
20048                            self.write(", ");
20049                        }
20050                        self.generate_expression(e)?;
20051                    }
20052                    self.write(")");
20053                    self.write_space();
20054                    self.write_keyword("END");
20055                } else {
20056                    for (i, e) in tuple.expressions.iter().enumerate() {
20057                        if i > 0 {
20058                            self.write(", ");
20059                        }
20060                        self.generate_expression(e)?;
20061                    }
20062                }
20063            } else {
20064                self.generate_expression(expr)?;
20065            }
20066        }
20067        let clickhouse_ignore_nulls_outside =
20068            matches!(self.config.dialect, Some(DialectType::ClickHouse));
20069        if let Some(ignore) = f.ignore_nulls.filter(|_| !clickhouse_ignore_nulls_outside) {
20070            self.write_space();
20071            if ignore {
20072                self.write_keyword("IGNORE NULLS");
20073            } else {
20074                self.write_keyword("RESPECT NULLS");
20075            }
20076        }
20077        self.write(")");
20078        if let Some(ignore) = f.ignore_nulls.filter(|_| clickhouse_ignore_nulls_outside) {
20079            self.write_space();
20080            if ignore {
20081                self.write_keyword("IGNORE NULLS");
20082            } else {
20083                self.write_keyword("RESPECT NULLS");
20084            }
20085        }
20086        if let Some(ref filter) = f.filter {
20087            self.write_space();
20088            self.write_keyword("FILTER");
20089            self.write("(");
20090            self.write_keyword("WHERE");
20091            self.write_space();
20092            self.generate_expression(filter)?;
20093            self.write(")");
20094        }
20095        Ok(())
20096    }
20097
20098    fn generate_agg_func(&mut self, name: &str, f: &AggFunc) -> Result<()> {
20099        // Apply function name normalization based on config
20100        let func_name: Cow<'_, str> = match self.config.normalize_functions {
20101            NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
20102            NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
20103            NormalizeFunctions::None => {
20104                // Use the original function name from parsing if available,
20105                // otherwise fall back to lowercase of the hardcoded constant
20106                if let Some(ref original) = f.name {
20107                    Cow::Owned(original.clone())
20108                } else {
20109                    Cow::Owned(name.to_ascii_lowercase())
20110                }
20111            }
20112        };
20113        self.write(func_name.as_ref());
20114        self.write("(");
20115        if f.distinct {
20116            self.write_keyword("DISTINCT");
20117            self.write_space();
20118        }
20119        // MODE() uses a NULL placeholder internally for its zero-arg ordered-set form.
20120        // Other aggregates may legitimately receive NULL as an explicit argument.
20121        let is_zero_arg_mode =
20122            name.eq_ignore_ascii_case("MODE") && matches!(f.this, Expression::Null(_));
20123        if !is_zero_arg_mode {
20124            self.generate_expression(&f.this)?;
20125        }
20126        // Generate IGNORE NULLS / RESPECT NULLS inside parens if config says so (BigQuery style)
20127        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
20128        if self.config.ignore_nulls_in_func
20129            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
20130        {
20131            match f.ignore_nulls {
20132                Some(true) => {
20133                    self.write_space();
20134                    self.write_keyword("IGNORE NULLS");
20135                }
20136                Some(false) => {
20137                    self.write_space();
20138                    self.write_keyword("RESPECT NULLS");
20139                }
20140                None => {}
20141            }
20142        }
20143        // Generate HAVING MAX/MIN if present (BigQuery syntax)
20144        // e.g., ANY_VALUE(fruit HAVING MAX sold)
20145        if let Some((ref expr, is_max)) = f.having_max {
20146            self.write_space();
20147            self.write_keyword("HAVING");
20148            self.write_space();
20149            if is_max {
20150                self.write_keyword("MAX");
20151            } else {
20152                self.write_keyword("MIN");
20153            }
20154            self.write_space();
20155            self.generate_expression(expr)?;
20156        }
20157        // Generate ORDER BY if present (for aggregates like ARRAY_AGG(x ORDER BY y))
20158        if !f.order_by.is_empty() {
20159            self.write_space();
20160            self.write_keyword("ORDER BY");
20161            self.write_space();
20162            for (i, ord) in f.order_by.iter().enumerate() {
20163                if i > 0 {
20164                    self.write(", ");
20165                }
20166                self.generate_ordered(ord)?;
20167            }
20168        }
20169        // Generate LIMIT if present (for aggregates like ARRAY_AGG(x ORDER BY y LIMIT 2))
20170        if let Some(ref limit) = f.limit {
20171            self.write_space();
20172            self.write_keyword("LIMIT");
20173            self.write_space();
20174            // Check if this is a Tuple representing LIMIT offset, count
20175            if let Expression::Tuple(t) = limit.as_ref() {
20176                if t.expressions.len() == 2 {
20177                    self.generate_expression(&t.expressions[0])?;
20178                    self.write(", ");
20179                    self.generate_expression(&t.expressions[1])?;
20180                } else {
20181                    self.generate_expression(limit)?;
20182                }
20183            } else {
20184                self.generate_expression(limit)?;
20185            }
20186        }
20187        self.write(")");
20188        // Generate IGNORE NULLS / RESPECT NULLS outside parens if config says so (standard style)
20189        // DuckDB doesn't support IGNORE NULLS / RESPECT NULLS in aggregate functions - skip it
20190        if !self.config.ignore_nulls_in_func
20191            && !matches!(self.config.dialect, Some(DialectType::DuckDB))
20192        {
20193            match f.ignore_nulls {
20194                Some(true) => {
20195                    self.write_space();
20196                    self.write_keyword("IGNORE NULLS");
20197                }
20198                Some(false) => {
20199                    self.write_space();
20200                    self.write_keyword("RESPECT NULLS");
20201                }
20202                None => {}
20203            }
20204        }
20205        if let Some(ref filter) = f.filter {
20206            self.write_space();
20207            self.write_keyword("FILTER");
20208            self.write("(");
20209            self.write_keyword("WHERE");
20210            self.write_space();
20211            self.generate_expression(filter)?;
20212            self.write(")");
20213        }
20214        Ok(())
20215    }
20216
20217    /// Generate FIRST/LAST aggregate functions with Hive/Spark2-style boolean argument
20218    /// for IGNORE NULLS. In Hive/Spark2, `FIRST(col) IGNORE NULLS` is written as `FIRST(col, TRUE)`.
20219    fn generate_agg_func_with_ignore_nulls_bool(&mut self, name: &str, f: &AggFunc) -> Result<()> {
20220        // For Hive/Spark2 dialects, convert IGNORE NULLS to boolean TRUE argument
20221        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
20222            // Create a modified copy without ignore_nulls, add TRUE as part of the output
20223            let func_name: Cow<'_, str> = match self.config.normalize_functions {
20224                NormalizeFunctions::Upper => Cow::Owned(name.to_ascii_uppercase()),
20225                NormalizeFunctions::Lower => Cow::Owned(name.to_ascii_lowercase()),
20226                NormalizeFunctions::None => {
20227                    if let Some(ref original) = f.name {
20228                        Cow::Owned(original.clone())
20229                    } else {
20230                        Cow::Owned(name.to_ascii_lowercase())
20231                    }
20232                }
20233            };
20234            self.write(func_name.as_ref());
20235            self.write("(");
20236            if f.distinct {
20237                self.write_keyword("DISTINCT");
20238                self.write_space();
20239            }
20240            if !matches!(f.this, Expression::Null(_)) {
20241                self.generate_expression(&f.this)?;
20242            }
20243            self.write(", ");
20244            self.write_keyword("TRUE");
20245            self.write(")");
20246            return Ok(());
20247        }
20248        self.generate_agg_func(name, f)
20249    }
20250
20251    fn generate_group_concat(&mut self, f: &GroupConcatFunc) -> Result<()> {
20252        self.write_keyword("GROUP_CONCAT");
20253        self.write("(");
20254        if f.distinct {
20255            self.write_keyword("DISTINCT");
20256            self.write_space();
20257        }
20258        self.generate_expression(&f.this)?;
20259        if let Some(ref order_by) = f.order_by {
20260            self.write_space();
20261            self.write_keyword("ORDER BY");
20262            self.write_space();
20263            for (i, ord) in order_by.iter().enumerate() {
20264                if i > 0 {
20265                    self.write(", ");
20266                }
20267                self.generate_ordered(ord)?;
20268            }
20269        }
20270        if let Some(ref sep) = f.separator {
20271            // SQLite uses GROUP_CONCAT(x, sep) syntax (comma-separated)
20272            // MySQL and others use GROUP_CONCAT(x SEPARATOR sep) syntax
20273            if matches!(
20274                self.config.dialect,
20275                Some(crate::dialects::DialectType::SQLite)
20276            ) {
20277                self.write(", ");
20278                self.generate_expression(sep)?;
20279            } else {
20280                self.write_space();
20281                self.write_keyword("SEPARATOR");
20282                self.write_space();
20283                self.generate_expression(sep)?;
20284            }
20285        }
20286        if let Some(ref limit) = f.limit {
20287            self.write_space();
20288            self.write_keyword("LIMIT");
20289            self.write_space();
20290            self.generate_expression(limit)?;
20291        }
20292        self.write(")");
20293        if let Some(ref filter) = f.filter {
20294            self.write_space();
20295            self.write_keyword("FILTER");
20296            self.write("(");
20297            self.write_keyword("WHERE");
20298            self.write_space();
20299            self.generate_expression(filter)?;
20300            self.write(")");
20301        }
20302        Ok(())
20303    }
20304
20305    fn generate_string_agg(&mut self, f: &StringAggFunc) -> Result<()> {
20306        let uses_within_group_order = matches!(
20307            self.config.dialect,
20308            Some(crate::dialects::DialectType::TSQL | crate::dialects::DialectType::Fabric)
20309        );
20310        self.write_keyword("STRING_AGG");
20311        self.write("(");
20312        if f.distinct {
20313            self.write_keyword("DISTINCT");
20314            self.write_space();
20315        }
20316        self.generate_expression(&f.this)?;
20317        if let Some(ref separator) = f.separator {
20318            self.write(", ");
20319            self.generate_expression(separator)?;
20320        }
20321        // TSQL/Fabric put aggregate ORDER BY in WITHIN GROUP after the closing paren.
20322        if !uses_within_group_order {
20323            if let Some(ref order_by) = f.order_by {
20324                self.write_space();
20325                self.write_keyword("ORDER BY");
20326                self.write_space();
20327                for (i, ord) in order_by.iter().enumerate() {
20328                    if i > 0 {
20329                        self.write(", ");
20330                    }
20331                    self.generate_ordered(ord)?;
20332                }
20333            }
20334        }
20335        if let Some(ref limit) = f.limit {
20336            self.write_space();
20337            self.write_keyword("LIMIT");
20338            self.write_space();
20339            self.generate_expression(limit)?;
20340        }
20341        self.write(")");
20342        if uses_within_group_order {
20343            if let Some(ref order_by) = f.order_by {
20344                self.write_space();
20345                self.write_keyword("WITHIN GROUP");
20346                self.write(" (");
20347                self.write_keyword("ORDER BY");
20348                self.write_space();
20349                for (i, ord) in order_by.iter().enumerate() {
20350                    if i > 0 {
20351                        self.write(", ");
20352                    }
20353                    self.generate_ordered(ord)?;
20354                }
20355                self.write(")");
20356            }
20357        }
20358        if let Some(ref filter) = f.filter {
20359            self.write_space();
20360            self.write_keyword("FILTER");
20361            self.write("(");
20362            self.write_keyword("WHERE");
20363            self.write_space();
20364            self.generate_expression(filter)?;
20365            self.write(")");
20366        }
20367        Ok(())
20368    }
20369
20370    fn generate_listagg(&mut self, f: &ListAggFunc) -> Result<()> {
20371        use crate::dialects::DialectType;
20372        self.write_keyword("LISTAGG");
20373        self.write("(");
20374        if f.distinct {
20375            self.write_keyword("DISTINCT");
20376            self.write_space();
20377        }
20378        self.generate_expression(&f.this)?;
20379        if let Some(ref sep) = f.separator {
20380            self.write(", ");
20381            self.generate_expression(sep)?;
20382        } else if matches!(
20383            self.config.dialect,
20384            Some(DialectType::Trino) | Some(DialectType::Presto)
20385        ) {
20386            // Trino/Presto require explicit separator; default to ','
20387            self.write(", ','");
20388        }
20389        if let Some(ref overflow) = f.on_overflow {
20390            self.write_space();
20391            self.write_keyword("ON OVERFLOW");
20392            self.write_space();
20393            match overflow {
20394                ListAggOverflow::Error => self.write_keyword("ERROR"),
20395                ListAggOverflow::Truncate { filler, with_count } => {
20396                    self.write_keyword("TRUNCATE");
20397                    if let Some(ref fill) = filler {
20398                        self.write_space();
20399                        self.generate_expression(fill)?;
20400                    }
20401                    if *with_count {
20402                        self.write_space();
20403                        self.write_keyword("WITH COUNT");
20404                    } else {
20405                        self.write_space();
20406                        self.write_keyword("WITHOUT COUNT");
20407                    }
20408                }
20409            }
20410        }
20411        self.write(")");
20412        if let Some(ref order_by) = f.order_by {
20413            self.write_space();
20414            self.write_keyword("WITHIN GROUP");
20415            self.write(" (");
20416            self.write_keyword("ORDER BY");
20417            self.write_space();
20418            for (i, ord) in order_by.iter().enumerate() {
20419                if i > 0 {
20420                    self.write(", ");
20421                }
20422                self.generate_ordered(ord)?;
20423            }
20424            self.write(")");
20425        }
20426        if let Some(ref filter) = f.filter {
20427            self.write_space();
20428            self.write_keyword("FILTER");
20429            self.write("(");
20430            self.write_keyword("WHERE");
20431            self.write_space();
20432            self.generate_expression(filter)?;
20433            self.write(")");
20434        }
20435        Ok(())
20436    }
20437
20438    fn generate_sum_if(&mut self, f: &SumIfFunc) -> Result<()> {
20439        self.write_keyword("SUM_IF");
20440        self.write("(");
20441        self.generate_expression(&f.this)?;
20442        self.write(", ");
20443        self.generate_expression(&f.condition)?;
20444        self.write(")");
20445        if let Some(ref filter) = f.filter {
20446            self.write_space();
20447            self.write_keyword("FILTER");
20448            self.write("(");
20449            self.write_keyword("WHERE");
20450            self.write_space();
20451            self.generate_expression(filter)?;
20452            self.write(")");
20453        }
20454        Ok(())
20455    }
20456
20457    fn generate_approx_percentile(&mut self, f: &ApproxPercentileFunc) -> Result<()> {
20458        self.write_keyword("APPROX_PERCENTILE");
20459        self.write("(");
20460        self.generate_expression(&f.this)?;
20461        self.write(", ");
20462        self.generate_expression(&f.percentile)?;
20463        if let Some(ref acc) = f.accuracy {
20464            self.write(", ");
20465            self.generate_expression(acc)?;
20466        }
20467        self.write(")");
20468        if let Some(ref filter) = f.filter {
20469            self.write_space();
20470            self.write_keyword("FILTER");
20471            self.write("(");
20472            self.write_keyword("WHERE");
20473            self.write_space();
20474            self.generate_expression(filter)?;
20475            self.write(")");
20476        }
20477        Ok(())
20478    }
20479
20480    fn generate_percentile(&mut self, name: &str, f: &PercentileFunc) -> Result<()> {
20481        self.write_keyword(name);
20482        self.write("(");
20483        self.generate_expression(&f.percentile)?;
20484        self.write(")");
20485        if let Some(ref order_by) = f.order_by {
20486            self.write_space();
20487            self.write_keyword("WITHIN GROUP");
20488            self.write(" (");
20489            self.write_keyword("ORDER BY");
20490            self.write_space();
20491            self.generate_expression(&f.this)?;
20492            for ord in order_by.iter() {
20493                if ord.desc {
20494                    self.write_space();
20495                    self.write_keyword("DESC");
20496                }
20497            }
20498            self.write(")");
20499        }
20500        if let Some(ref filter) = f.filter {
20501            self.write_space();
20502            self.write_keyword("FILTER");
20503            self.write("(");
20504            self.write_keyword("WHERE");
20505            self.write_space();
20506            self.generate_expression(filter)?;
20507            self.write(")");
20508        }
20509        Ok(())
20510    }
20511
20512    // Window function generators
20513
20514    fn generate_ntile(&mut self, f: &NTileFunc) -> Result<()> {
20515        self.write_keyword("NTILE");
20516        self.write("(");
20517        if let Some(num_buckets) = &f.num_buckets {
20518            self.generate_expression(num_buckets)?;
20519        }
20520        if let Some(order_by) = &f.order_by {
20521            self.write_keyword(" ORDER BY ");
20522            for (i, ob) in order_by.iter().enumerate() {
20523                if i > 0 {
20524                    self.write(", ");
20525                }
20526                self.generate_ordered(ob)?;
20527            }
20528        }
20529        self.write(")");
20530        Ok(())
20531    }
20532
20533    fn generate_lead_lag(&mut self, name: &str, f: &LeadLagFunc) -> Result<()> {
20534        self.write_keyword(name);
20535        self.write("(");
20536        self.generate_expression(&f.this)?;
20537        if let Some(ref offset) = f.offset {
20538            self.write(", ");
20539            self.generate_expression(offset)?;
20540            if let Some(ref default) = f.default {
20541                self.write(", ");
20542                self.generate_expression(default)?;
20543            }
20544        }
20545        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery
20546        if self.config.ignore_nulls_in_func {
20547            match f.ignore_nulls {
20548                Some(true) => {
20549                    self.write_space();
20550                    self.write_keyword("IGNORE NULLS");
20551                }
20552                Some(false) => {
20553                    self.write_space();
20554                    self.write_keyword("RESPECT NULLS");
20555                }
20556                None => {}
20557            }
20558        }
20559        self.write(")");
20560        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20561        if !self.config.ignore_nulls_in_func {
20562            match f.ignore_nulls {
20563                Some(true) => {
20564                    self.write_space();
20565                    self.write_keyword("IGNORE NULLS");
20566                }
20567                Some(false) => {
20568                    self.write_space();
20569                    self.write_keyword("RESPECT NULLS");
20570                }
20571                None => {}
20572            }
20573        }
20574        Ok(())
20575    }
20576
20577    fn generate_value_func(&mut self, name: &str, f: &ValueFunc) -> Result<()> {
20578        self.write_keyword(name);
20579        self.write("(");
20580        self.generate_expression(&f.this)?;
20581        // ORDER BY inside parens (e.g., DuckDB: LAST_VALUE(x ORDER BY x))
20582        if !f.order_by.is_empty() {
20583            self.write_space();
20584            self.write_keyword("ORDER BY");
20585            self.write_space();
20586            for (i, ordered) in f.order_by.iter().enumerate() {
20587                if i > 0 {
20588                    self.write(", ");
20589                }
20590                self.generate_ordered(ordered)?;
20591            }
20592        }
20593        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20594        if self.config.ignore_nulls_in_func {
20595            match f.ignore_nulls {
20596                Some(true) => {
20597                    self.write_space();
20598                    self.write_keyword("IGNORE NULLS");
20599                }
20600                Some(false) => {
20601                    self.write_space();
20602                    self.write_keyword("RESPECT NULLS");
20603                }
20604                None => {}
20605            }
20606        }
20607        self.write(")");
20608        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20609        if !self.config.ignore_nulls_in_func {
20610            match f.ignore_nulls {
20611                Some(true) => {
20612                    self.write_space();
20613                    self.write_keyword("IGNORE NULLS");
20614                }
20615                Some(false) => {
20616                    self.write_space();
20617                    self.write_keyword("RESPECT NULLS");
20618                }
20619                None => {}
20620            }
20621        }
20622        Ok(())
20623    }
20624
20625    /// Generate FIRST_VALUE/LAST_VALUE with Hive/Spark2-style boolean argument for IGNORE NULLS.
20626    /// In Hive/Spark2, `FIRST_VALUE(col) IGNORE NULLS` is written as `FIRST_VALUE(col, TRUE)`.
20627    fn generate_value_func_with_ignore_nulls_bool(
20628        &mut self,
20629        name: &str,
20630        f: &ValueFunc,
20631    ) -> Result<()> {
20632        if matches!(self.config.dialect, Some(DialectType::Hive)) && f.ignore_nulls == Some(true) {
20633            self.write_keyword(name);
20634            self.write("(");
20635            self.generate_expression(&f.this)?;
20636            self.write(", ");
20637            self.write_keyword("TRUE");
20638            self.write(")");
20639            return Ok(());
20640        }
20641        self.generate_value_func(name, f)
20642    }
20643
20644    fn generate_nth_value(&mut self, f: &NthValueFunc) -> Result<()> {
20645        self.write_keyword("NTH_VALUE");
20646        self.write("(");
20647        self.generate_expression(&f.this)?;
20648        self.write(", ");
20649        self.generate_expression(&f.offset)?;
20650        // IGNORE NULLS / RESPECT NULLS inside parens for dialects like BigQuery, DuckDB
20651        if self.config.ignore_nulls_in_func {
20652            match f.ignore_nulls {
20653                Some(true) => {
20654                    self.write_space();
20655                    self.write_keyword("IGNORE NULLS");
20656                }
20657                Some(false) => {
20658                    self.write_space();
20659                    self.write_keyword("RESPECT NULLS");
20660                }
20661                None => {}
20662            }
20663        }
20664        self.write(")");
20665        // FROM FIRST / FROM LAST (Snowflake-specific, before IGNORE/RESPECT NULLS)
20666        if matches!(
20667            self.config.dialect,
20668            Some(crate::dialects::DialectType::Snowflake)
20669        ) {
20670            match f.from_first {
20671                Some(true) => {
20672                    self.write_space();
20673                    self.write_keyword("FROM FIRST");
20674                }
20675                Some(false) => {
20676                    self.write_space();
20677                    self.write_keyword("FROM LAST");
20678                }
20679                None => {}
20680            }
20681        }
20682        // IGNORE NULLS / RESPECT NULLS outside parens for other dialects
20683        if !self.config.ignore_nulls_in_func {
20684            match f.ignore_nulls {
20685                Some(true) => {
20686                    self.write_space();
20687                    self.write_keyword("IGNORE NULLS");
20688                }
20689                Some(false) => {
20690                    self.write_space();
20691                    self.write_keyword("RESPECT NULLS");
20692                }
20693                None => {}
20694            }
20695        }
20696        Ok(())
20697    }
20698
20699    // Additional string function generators
20700
20701    fn generate_position(&mut self, f: &PositionFunc) -> Result<()> {
20702        // Standard syntax: POSITION(substr IN str)
20703        // ClickHouse prefers comma syntax with reversed arg order: POSITION(str, substr[, start])
20704        if matches!(
20705            self.config.dialect,
20706            Some(crate::dialects::DialectType::ClickHouse)
20707        ) {
20708            self.write_keyword("POSITION");
20709            self.write("(");
20710            self.generate_expression(&f.string)?;
20711            self.write(", ");
20712            self.generate_expression(&f.substring)?;
20713            if let Some(ref start) = f.start {
20714                self.write(", ");
20715                self.generate_expression(start)?;
20716            }
20717            self.write(")");
20718            return Ok(());
20719        }
20720
20721        self.write_keyword("POSITION");
20722        self.write("(");
20723        self.generate_expression(&f.substring)?;
20724        self.write_space();
20725        self.write_keyword("IN");
20726        self.write_space();
20727        self.generate_expression(&f.string)?;
20728        if let Some(ref start) = f.start {
20729            self.write(", ");
20730            self.generate_expression(start)?;
20731        }
20732        self.write(")");
20733        Ok(())
20734    }
20735
20736    // Additional math function generators
20737
20738    fn generate_rand(&mut self, f: &Rand) -> Result<()> {
20739        // Teradata RANDOM(lower, upper)
20740        if f.lower.is_some() || f.upper.is_some() {
20741            self.write_keyword("RANDOM");
20742            self.write("(");
20743            if let Some(ref lower) = f.lower {
20744                self.generate_expression(lower)?;
20745            }
20746            if let Some(ref upper) = f.upper {
20747                self.write(", ");
20748                self.generate_expression(upper)?;
20749            }
20750            self.write(")");
20751            return Ok(());
20752        }
20753        // Snowflake uses RANDOM instead of RAND, DuckDB uses RANDOM without seed
20754        let func_name = match self.config.dialect {
20755            Some(crate::dialects::DialectType::Snowflake)
20756            | Some(crate::dialects::DialectType::DuckDB) => "RANDOM",
20757            _ => "RAND",
20758        };
20759        self.write_keyword(func_name);
20760        self.write("(");
20761        // DuckDB doesn't support seeded RANDOM, so skip the seed
20762        if !matches!(
20763            self.config.dialect,
20764            Some(crate::dialects::DialectType::DuckDB)
20765        ) {
20766            if let Some(ref seed) = f.seed {
20767                self.generate_expression(seed)?;
20768            }
20769        }
20770        self.write(")");
20771        Ok(())
20772    }
20773
20774    fn generate_truncate_func(&mut self, f: &TruncateFunc) -> Result<()> {
20775        self.write_keyword("TRUNCATE");
20776        self.write("(");
20777        self.generate_expression(&f.this)?;
20778        if let Some(ref decimals) = f.decimals {
20779            self.write(", ");
20780            self.generate_expression(decimals)?;
20781        }
20782        self.write(")");
20783        Ok(())
20784    }
20785
20786    // Control flow generators
20787
20788    fn generate_decode(&mut self, f: &DecodeFunc) -> Result<()> {
20789        self.write_keyword("DECODE");
20790        self.write("(");
20791        self.generate_expression(&f.this)?;
20792        for (search, result) in &f.search_results {
20793            self.write(", ");
20794            self.generate_expression(search)?;
20795            self.write(", ");
20796            self.generate_expression(result)?;
20797        }
20798        if let Some(ref default) = f.default {
20799            self.write(", ");
20800            self.generate_expression(default)?;
20801        }
20802        self.write(")");
20803        Ok(())
20804    }
20805
20806    // Date/time function generators
20807
20808    fn generate_date_format(&mut self, name: &str, f: &DateFormatFunc) -> Result<()> {
20809        self.write_keyword(name);
20810        self.write("(");
20811        self.generate_expression(&f.this)?;
20812        self.write(", ");
20813        self.generate_expression(&f.format)?;
20814        self.write(")");
20815        Ok(())
20816    }
20817
20818    fn generate_from_unixtime(&mut self, f: &FromUnixtimeFunc) -> Result<()> {
20819        self.write_keyword("FROM_UNIXTIME");
20820        self.write("(");
20821        self.generate_expression(&f.this)?;
20822        if let Some(ref format) = f.format {
20823            self.write(", ");
20824            self.generate_expression(format)?;
20825        }
20826        self.write(")");
20827        Ok(())
20828    }
20829
20830    fn generate_unix_timestamp(&mut self, f: &UnixTimestampFunc) -> Result<()> {
20831        self.write_keyword("UNIX_TIMESTAMP");
20832        self.write("(");
20833        if let Some(ref expr) = f.this {
20834            self.generate_expression(expr)?;
20835            if let Some(ref format) = f.format {
20836                self.write(", ");
20837                self.generate_expression(format)?;
20838            }
20839        } else if matches!(
20840            self.config.dialect,
20841            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
20842        ) {
20843            // Spark/Hive: UNIX_TIMESTAMP() -> UNIX_TIMESTAMP(CURRENT_TIMESTAMP())
20844            self.write_keyword("CURRENT_TIMESTAMP");
20845            self.write("()");
20846        }
20847        self.write(")");
20848        Ok(())
20849    }
20850
20851    fn generate_make_date(&mut self, f: &MakeDateFunc) -> Result<()> {
20852        self.write_keyword("MAKE_DATE");
20853        self.write("(");
20854        self.generate_expression(&f.year)?;
20855        self.write(", ");
20856        self.generate_expression(&f.month)?;
20857        self.write(", ");
20858        self.generate_expression(&f.day)?;
20859        self.write(")");
20860        Ok(())
20861    }
20862
20863    fn generate_make_timestamp(&mut self, f: &MakeTimestampFunc) -> Result<()> {
20864        self.write_keyword("MAKE_TIMESTAMP");
20865        self.write("(");
20866        self.generate_expression(&f.year)?;
20867        self.write(", ");
20868        self.generate_expression(&f.month)?;
20869        self.write(", ");
20870        self.generate_expression(&f.day)?;
20871        self.write(", ");
20872        self.generate_expression(&f.hour)?;
20873        self.write(", ");
20874        self.generate_expression(&f.minute)?;
20875        self.write(", ");
20876        self.generate_expression(&f.second)?;
20877        if let Some(ref tz) = f.timezone {
20878            self.write(", ");
20879            self.generate_expression(tz)?;
20880        }
20881        self.write(")");
20882        Ok(())
20883    }
20884
20885    /// Extract field names from a struct expression (either Struct or Function named STRUCT with Alias args)
20886    fn extract_struct_field_names(expr: &Expression) -> Option<Vec<String>> {
20887        match expr {
20888            Expression::Struct(s) => {
20889                if s.fields.iter().all(|(name, _)| name.is_some()) {
20890                    Some(
20891                        s.fields
20892                            .iter()
20893                            .map(|(name, _)| name.as_deref().unwrap_or("").to_string())
20894                            .collect(),
20895                    )
20896                } else {
20897                    None
20898                }
20899            }
20900            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20901                // Check if all args are Alias (named fields)
20902                if f.args.iter().all(|a| matches!(a, Expression::Alias(_))) {
20903                    Some(
20904                        f.args
20905                            .iter()
20906                            .filter_map(|a| {
20907                                if let Expression::Alias(alias) = a {
20908                                    Some(alias.alias.name.clone())
20909                                } else {
20910                                    None
20911                                }
20912                            })
20913                            .collect(),
20914                    )
20915                } else {
20916                    None
20917                }
20918            }
20919            _ => None,
20920        }
20921    }
20922
20923    /// Check if a struct expression has any unnamed fields
20924    fn struct_has_unnamed_fields(expr: &Expression) -> bool {
20925        match expr {
20926            Expression::Struct(s) => s.fields.iter().any(|(name, _)| name.is_none()),
20927            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20928                f.args.iter().any(|a| !matches!(a, Expression::Alias(_)))
20929            }
20930            _ => false,
20931        }
20932    }
20933
20934    /// Get the field count of a struct expression
20935    fn struct_field_count(expr: &Expression) -> usize {
20936        match expr {
20937            Expression::Struct(s) => s.fields.len(),
20938            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => f.args.len(),
20939            _ => 0,
20940        }
20941    }
20942
20943    /// Apply field names to an unnamed struct expression, producing a new expression with names
20944    fn apply_struct_field_names(expr: &Expression, field_names: &[String]) -> Expression {
20945        match expr {
20946            Expression::Struct(s) => {
20947                let mut new_fields = Vec::with_capacity(s.fields.len());
20948                for (i, (name, value)) in s.fields.iter().enumerate() {
20949                    if name.is_none() && i < field_names.len() {
20950                        new_fields.push((Some(field_names[i].clone()), value.clone()));
20951                    } else {
20952                        new_fields.push((name.clone(), value.clone()));
20953                    }
20954                }
20955                Expression::Struct(Box::new(crate::expressions::Struct { fields: new_fields }))
20956            }
20957            Expression::Function(f) if f.name.eq_ignore_ascii_case("STRUCT") => {
20958                let mut new_args = Vec::with_capacity(f.args.len());
20959                for (i, arg) in f.args.iter().enumerate() {
20960                    if !matches!(arg, Expression::Alias(_)) && i < field_names.len() {
20961                        // Wrap the value in an Alias with the inherited name
20962                        new_args.push(Expression::Alias(Box::new(crate::expressions::Alias {
20963                            this: arg.clone(),
20964                            alias: crate::expressions::Identifier::new(field_names[i].clone()),
20965                            column_aliases: Vec::new(),
20966                            alias_explicit_as: false,
20967                            alias_keyword: None,
20968                            pre_alias_comments: Vec::new(),
20969                            trailing_comments: Vec::new(),
20970                            inferred_type: None,
20971                        })));
20972                    } else {
20973                        new_args.push(arg.clone());
20974                    }
20975                }
20976                Expression::Function(Box::new(crate::expressions::Function {
20977                    name: f.name.clone(),
20978                    args: new_args,
20979                    distinct: f.distinct,
20980                    trailing_comments: f.trailing_comments.clone(),
20981                    use_bracket_syntax: f.use_bracket_syntax,
20982                    no_parens: f.no_parens,
20983                    quoted: f.quoted,
20984                    span: None,
20985                    inferred_type: None,
20986                }))
20987            }
20988            _ => expr.clone(),
20989        }
20990    }
20991
20992    /// Propagate struct field names from the first struct in an array to subsequent unnamed structs.
20993    /// This implements BigQuery's implicit field name inheritance for struct arrays.
20994    /// Handles both Expression::Struct and Expression::Function named "STRUCT".
20995    fn inherit_struct_field_names(expressions: &[Expression]) -> Vec<Expression> {
20996        let first = match expressions.first() {
20997            Some(e) => e,
20998            None => return expressions.to_vec(),
20999        };
21000
21001        let field_names = match Self::extract_struct_field_names(first) {
21002            Some(names) if !names.is_empty() => names,
21003            _ => return expressions.to_vec(),
21004        };
21005
21006        let mut result = Vec::with_capacity(expressions.len());
21007        for (idx, expr) in expressions.iter().enumerate() {
21008            if idx == 0 {
21009                result.push(expr.clone());
21010                continue;
21011            }
21012            // Check if this is a struct with unnamed fields that needs name propagation
21013            if Self::struct_field_count(expr) == field_names.len()
21014                && Self::struct_has_unnamed_fields(expr)
21015            {
21016                result.push(Self::apply_struct_field_names(expr, &field_names));
21017            } else {
21018                result.push(expr.clone());
21019            }
21020        }
21021        result
21022    }
21023
21024    // Array function generators
21025
21026    fn generate_array_constructor(&mut self, f: &ArrayConstructor) -> Result<()> {
21027        // Apply struct name inheritance for target dialects that need it
21028        // (DuckDB, Spark, Databricks, Hive, Snowflake, Presto, Trino)
21029        let needs_inheritance = matches!(
21030            self.config.dialect,
21031            Some(DialectType::DuckDB)
21032                | Some(DialectType::Spark)
21033                | Some(DialectType::Databricks)
21034                | Some(DialectType::Hive)
21035                | Some(DialectType::Snowflake)
21036                | Some(DialectType::Presto)
21037                | Some(DialectType::Trino)
21038        );
21039        let propagated: Vec<Expression>;
21040        let expressions = if needs_inheritance && f.expressions.len() > 1 {
21041            propagated = Self::inherit_struct_field_names(&f.expressions);
21042            &propagated
21043        } else {
21044            &f.expressions
21045        };
21046
21047        // Check if elements should be split onto multiple lines (pretty + too wide)
21048        let should_split = if self.config.pretty && !expressions.is_empty() {
21049            let mut expr_strings: Vec<String> = Vec::with_capacity(expressions.len());
21050            for expr in expressions {
21051                let mut temp_gen = Generator::with_arc_config(self.config.clone());
21052                Arc::make_mut(&mut temp_gen.config).pretty = false;
21053                temp_gen.generate_expression(expr)?;
21054                expr_strings.push(temp_gen.output);
21055            }
21056            self.too_wide(&expr_strings)
21057        } else {
21058            false
21059        };
21060
21061        if f.bracket_notation {
21062            // For Spark/Databricks, use ARRAY(...) with parens
21063            // For Presto/Trino/PostgreSQL, use ARRAY[...] with keyword prefix
21064            // For others (DuckDB, Snowflake), use bare [...]
21065            let (open, close) = match self.config.dialect {
21066                None
21067                | Some(DialectType::Generic)
21068                | Some(DialectType::Spark)
21069                | Some(DialectType::Databricks)
21070                | Some(DialectType::Hive) => {
21071                    self.write_keyword("ARRAY");
21072                    ("(", ")")
21073                }
21074                Some(DialectType::Presto)
21075                | Some(DialectType::Trino)
21076                | Some(DialectType::PostgreSQL)
21077                | Some(DialectType::Redshift)
21078                | Some(DialectType::Materialize)
21079                | Some(DialectType::RisingWave)
21080                | Some(DialectType::CockroachDB) => {
21081                    self.write_keyword("ARRAY");
21082                    ("[", "]")
21083                }
21084                _ => ("[", "]"),
21085            };
21086            self.write(open);
21087            if should_split {
21088                self.write_newline();
21089                self.indent_level += 1;
21090                for (i, expr) in expressions.iter().enumerate() {
21091                    self.write_indent();
21092                    self.generate_expression(expr)?;
21093                    if i + 1 < expressions.len() {
21094                        self.write(",");
21095                    }
21096                    self.write_newline();
21097                }
21098                self.indent_level -= 1;
21099                self.write_indent();
21100            } else {
21101                for (i, expr) in expressions.iter().enumerate() {
21102                    if i > 0 {
21103                        self.write(", ");
21104                    }
21105                    self.generate_expression(expr)?;
21106                }
21107            }
21108            self.write(close);
21109        } else {
21110            // Use LIST keyword if that was the original syntax (DuckDB)
21111            if f.use_list_keyword {
21112                self.write_keyword("LIST");
21113            } else {
21114                self.write_keyword("ARRAY");
21115            }
21116            // For Spark/Hive, always use ARRAY(...) with parens
21117            // Also use parens for BigQuery when the array contains a subquery (ARRAY(SELECT ...))
21118            let has_subquery = expressions
21119                .iter()
21120                .any(|e| matches!(e, Expression::Select(_)));
21121            let (open, close) = if matches!(
21122                self.config.dialect,
21123                Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive)
21124            ) || (matches!(self.config.dialect, Some(DialectType::BigQuery))
21125                && has_subquery)
21126            {
21127                ("(", ")")
21128            } else {
21129                ("[", "]")
21130            };
21131            self.write(open);
21132            if should_split {
21133                self.write_newline();
21134                self.indent_level += 1;
21135                for (i, expr) in expressions.iter().enumerate() {
21136                    self.write_indent();
21137                    self.generate_expression(expr)?;
21138                    if i + 1 < expressions.len() {
21139                        self.write(",");
21140                    }
21141                    self.write_newline();
21142                }
21143                self.indent_level -= 1;
21144                self.write_indent();
21145            } else {
21146                for (i, expr) in expressions.iter().enumerate() {
21147                    if i > 0 {
21148                        self.write(", ");
21149                    }
21150                    self.generate_expression(expr)?;
21151                }
21152            }
21153            self.write(close);
21154        }
21155        Ok(())
21156    }
21157
21158    fn generate_array_sort(&mut self, f: &ArraySortFunc) -> Result<()> {
21159        self.write_keyword("ARRAY_SORT");
21160        self.write("(");
21161        self.generate_expression(&f.this)?;
21162        if let Some(ref comp) = f.comparator {
21163            self.write(", ");
21164            self.generate_expression(comp)?;
21165        }
21166        self.write(")");
21167        Ok(())
21168    }
21169
21170    fn generate_array_join(&mut self, name: &str, f: &ArrayJoinFunc) -> Result<()> {
21171        self.write_keyword(name);
21172        self.write("(");
21173        self.generate_expression(&f.this)?;
21174        self.write(", ");
21175        self.generate_expression(&f.separator)?;
21176        if let Some(ref null_rep) = f.null_replacement {
21177            self.write(", ");
21178            self.generate_expression(null_rep)?;
21179        }
21180        self.write(")");
21181        Ok(())
21182    }
21183
21184    fn generate_unnest(&mut self, f: &UnnestFunc) -> Result<()> {
21185        self.write_keyword("UNNEST");
21186        self.write("(");
21187        self.generate_expression(&f.this)?;
21188        for extra in &f.expressions {
21189            self.write(", ");
21190            self.generate_expression(extra)?;
21191        }
21192        self.write(")");
21193        if f.with_ordinality {
21194            self.write_space();
21195            if self.config.unnest_with_ordinality {
21196                // Presto/Trino: UNNEST(arr) WITH ORDINALITY [AS alias]
21197                self.write_keyword("WITH ORDINALITY");
21198            } else if f.offset_alias.is_some() {
21199                // BigQuery: UNNEST(arr) [AS col] WITH OFFSET AS pos
21200                // Alias (if any) comes BEFORE WITH OFFSET
21201                if let Some(ref alias) = f.alias {
21202                    self.write_keyword("AS");
21203                    self.write_space();
21204                    self.generate_identifier(alias)?;
21205                    self.write_space();
21206                }
21207                self.write_keyword("WITH OFFSET");
21208                if let Some(ref offset_alias) = f.offset_alias {
21209                    self.write_space();
21210                    self.write_keyword("AS");
21211                    self.write_space();
21212                    self.generate_identifier(offset_alias)?;
21213                }
21214            } else {
21215                // WITH OFFSET (BigQuery identity) - add default "AS offset" if no explicit alias
21216                self.write_keyword("WITH OFFSET");
21217                if f.alias.is_none() {
21218                    self.write(" AS offset");
21219                }
21220            }
21221        }
21222        if let Some(ref alias) = f.alias {
21223            // Add alias for: non-WITH-OFFSET cases, Presto/Trino WITH ORDINALITY, or BigQuery WITH OFFSET + alias (no offset_alias)
21224            let should_add_alias = if !f.with_ordinality {
21225                true
21226            } else if self.config.unnest_with_ordinality {
21227                // Presto/Trino: alias comes after WITH ORDINALITY
21228                true
21229            } else if f.offset_alias.is_some() {
21230                // BigQuery expansion: alias already handled above
21231                false
21232            } else {
21233                // BigQuery WITH OFFSET + alias but no offset_alias: alias comes after
21234                true
21235            };
21236            if should_add_alias {
21237                self.write_space();
21238                self.write_keyword("AS");
21239                self.write_space();
21240                self.generate_identifier(alias)?;
21241            }
21242        }
21243        Ok(())
21244    }
21245
21246    fn generate_array_filter(&mut self, f: &ArrayFilterFunc) -> Result<()> {
21247        self.write_keyword("FILTER");
21248        self.write("(");
21249        self.generate_expression(&f.this)?;
21250        self.write(", ");
21251        self.generate_expression(&f.filter)?;
21252        self.write(")");
21253        Ok(())
21254    }
21255
21256    fn generate_array_transform(&mut self, f: &ArrayTransformFunc) -> Result<()> {
21257        self.write_keyword("TRANSFORM");
21258        self.write("(");
21259        self.generate_expression(&f.this)?;
21260        self.write(", ");
21261        self.generate_expression(&f.transform)?;
21262        self.write(")");
21263        Ok(())
21264    }
21265
21266    fn generate_sequence(&mut self, name: &str, f: &SequenceFunc) -> Result<()> {
21267        self.write_keyword(name);
21268        self.write("(");
21269        self.generate_expression(&f.start)?;
21270        self.write(", ");
21271        self.generate_expression(&f.stop)?;
21272        if let Some(ref step) = f.step {
21273            self.write(", ");
21274            self.generate_expression(step)?;
21275        }
21276        self.write(")");
21277        Ok(())
21278    }
21279
21280    // Struct function generators
21281
21282    fn generate_struct_constructor(&mut self, f: &StructConstructor) -> Result<()> {
21283        self.write_keyword("STRUCT");
21284        self.write("(");
21285        for (i, (name, expr)) in f.fields.iter().enumerate() {
21286            if i > 0 {
21287                self.write(", ");
21288            }
21289            if let Some(ref id) = name {
21290                self.generate_identifier(id)?;
21291                self.write(" ");
21292                self.write_keyword("AS");
21293                self.write(" ");
21294            }
21295            self.generate_expression(expr)?;
21296        }
21297        self.write(")");
21298        Ok(())
21299    }
21300
21301    /// Convert BigQuery STRUCT function (parsed as Function with Alias args) to target dialect
21302    fn generate_struct_function_cross_dialect(&mut self, func: &Function) -> Result<()> {
21303        // Extract named/unnamed fields from function args
21304        // Args are either Alias(this=value, alias=name) for named or plain expressions for unnamed
21305        let mut names: Vec<Option<String>> = Vec::new();
21306        let mut values: Vec<&Expression> = Vec::new();
21307        let mut all_named = true;
21308
21309        for arg in &func.args {
21310            match arg {
21311                Expression::Alias(a) => {
21312                    names.push(Some(a.alias.name.clone()));
21313                    values.push(&a.this);
21314                }
21315                _ => {
21316                    names.push(None);
21317                    values.push(arg);
21318                    all_named = false;
21319                }
21320            }
21321        }
21322
21323        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
21324            // DuckDB: {'name': value, ...} for named, {'_0': value, ...} for unnamed
21325            self.write("{");
21326            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21327                if i > 0 {
21328                    self.write(", ");
21329                }
21330                if let Some(n) = name {
21331                    self.write("'");
21332                    self.write(n);
21333                    self.write("'");
21334                } else {
21335                    self.write("'_");
21336                    self.write(&i.to_string());
21337                    self.write("'");
21338                }
21339                self.write(": ");
21340                self.generate_expression(value)?;
21341            }
21342            self.write("}");
21343            return Ok(());
21344        }
21345
21346        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
21347            // Snowflake: OBJECT_CONSTRUCT('name', value, ...)
21348            self.write_keyword("OBJECT_CONSTRUCT");
21349            self.write("(");
21350            for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21351                if i > 0 {
21352                    self.write(", ");
21353                }
21354                if let Some(n) = name {
21355                    self.write("'");
21356                    self.write(n);
21357                    self.write("'");
21358                } else {
21359                    self.write("'_");
21360                    self.write(&i.to_string());
21361                    self.write("'");
21362                }
21363                self.write(", ");
21364                self.generate_expression(value)?;
21365            }
21366            self.write(")");
21367            return Ok(());
21368        }
21369
21370        if matches!(
21371            self.config.dialect,
21372            Some(DialectType::Presto) | Some(DialectType::Trino)
21373        ) {
21374            if all_named && !names.is_empty() {
21375                // Presto/Trino: CAST(ROW(values...) AS ROW(name TYPE, ...))
21376                // Need to infer types from values
21377                self.write_keyword("CAST");
21378                self.write("(");
21379                self.write_keyword("ROW");
21380                self.write("(");
21381                for (i, value) in values.iter().enumerate() {
21382                    if i > 0 {
21383                        self.write(", ");
21384                    }
21385                    self.generate_expression(value)?;
21386                }
21387                self.write(")");
21388                self.write(" ");
21389                self.write_keyword("AS");
21390                self.write(" ");
21391                self.write_keyword("ROW");
21392                self.write("(");
21393                for (i, (name, value)) in names.iter().zip(values.iter()).enumerate() {
21394                    if i > 0 {
21395                        self.write(", ");
21396                    }
21397                    if let Some(n) = name {
21398                        self.write(n);
21399                    }
21400                    self.write(" ");
21401                    let type_str = Self::infer_sql_type_for_presto(value);
21402                    self.write_keyword(&type_str);
21403                }
21404                self.write(")");
21405                self.write(")");
21406            } else {
21407                // Unnamed: ROW(values...)
21408                self.write_keyword("ROW");
21409                self.write("(");
21410                for (i, value) in values.iter().enumerate() {
21411                    if i > 0 {
21412                        self.write(", ");
21413                    }
21414                    self.generate_expression(value)?;
21415                }
21416                self.write(")");
21417            }
21418            return Ok(());
21419        }
21420
21421        // Default: ROW(values...) for other dialects
21422        self.write_keyword("ROW");
21423        self.write("(");
21424        for (i, value) in values.iter().enumerate() {
21425            if i > 0 {
21426                self.write(", ");
21427            }
21428            self.generate_expression(value)?;
21429        }
21430        self.write(")");
21431        Ok(())
21432    }
21433
21434    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
21435    fn infer_sql_type_for_presto(expr: &Expression) -> String {
21436        match expr {
21437            Expression::Literal(lit)
21438                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
21439            {
21440                "VARCHAR".to_string()
21441            }
21442            Expression::Literal(lit)
21443                if matches!(lit.as_ref(), crate::expressions::Literal::Number(_)) =>
21444            {
21445                let crate::expressions::Literal::Number(n) = lit.as_ref() else {
21446                    unreachable!()
21447                };
21448                if n.contains('.') {
21449                    "DOUBLE".to_string()
21450                } else {
21451                    "INTEGER".to_string()
21452                }
21453            }
21454            Expression::Boolean(_) => "BOOLEAN".to_string(),
21455            Expression::Literal(lit)
21456                if matches!(lit.as_ref(), crate::expressions::Literal::Date(_)) =>
21457            {
21458                "DATE".to_string()
21459            }
21460            Expression::Literal(lit)
21461                if matches!(lit.as_ref(), crate::expressions::Literal::Timestamp(_)) =>
21462            {
21463                "TIMESTAMP".to_string()
21464            }
21465            Expression::Literal(lit)
21466                if matches!(lit.as_ref(), crate::expressions::Literal::Datetime(_)) =>
21467            {
21468                "TIMESTAMP".to_string()
21469            }
21470            Expression::Array(_) | Expression::ArrayFunc(_) => {
21471                // Try to infer element type from first element
21472                "ARRAY(VARCHAR)".to_string()
21473            }
21474            // For nested structs - generate a nested ROW type by inspecting fields
21475            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
21476            Expression::Function(f) => {
21477                if f.name.eq_ignore_ascii_case("STRUCT") {
21478                    "ROW".to_string()
21479                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
21480                    "DATE".to_string()
21481                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
21482                    || f.name.eq_ignore_ascii_case("NOW")
21483                {
21484                    "TIMESTAMP".to_string()
21485                } else {
21486                    "VARCHAR".to_string()
21487                }
21488            }
21489            _ => "VARCHAR".to_string(),
21490        }
21491    }
21492
21493    fn generate_struct_extract(&mut self, f: &StructExtractFunc) -> Result<()> {
21494        // DuckDB uses STRUCT_EXTRACT function syntax
21495        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
21496            self.write_keyword("STRUCT_EXTRACT");
21497            self.write("(");
21498            self.generate_expression(&f.this)?;
21499            self.write(", ");
21500            // Output field name as string literal
21501            self.write("'");
21502            self.write(&f.field.name);
21503            self.write("'");
21504            self.write(")");
21505            return Ok(());
21506        }
21507        self.generate_expression(&f.this)?;
21508        self.write(".");
21509        self.generate_identifier(&f.field)
21510    }
21511
21512    fn generate_named_struct(&mut self, f: &NamedStructFunc) -> Result<()> {
21513        if matches!(
21514            self.config.dialect,
21515            Some(DialectType::Spark | DialectType::Databricks)
21516        ) {
21517            self.write_keyword("STRUCT");
21518            self.write("(");
21519            for (i, (name, value)) in f.pairs.iter().enumerate() {
21520                if i > 0 {
21521                    self.write(", ");
21522                }
21523                self.generate_expression(value)?;
21524                self.write(" ");
21525                self.write_keyword("AS");
21526                self.write(" ");
21527                if let Expression::Literal(lit) = name {
21528                    if let Literal::String(field_name) = lit.as_ref() {
21529                        self.generate_identifier(&Identifier::new(field_name))?;
21530                    } else {
21531                        self.generate_expression(name)?;
21532                    }
21533                } else {
21534                    self.generate_expression(name)?;
21535                }
21536            }
21537            self.write(")");
21538            return Ok(());
21539        }
21540
21541        self.write_keyword("NAMED_STRUCT");
21542        self.write("(");
21543        for (i, (name, value)) in f.pairs.iter().enumerate() {
21544            if i > 0 {
21545                self.write(", ");
21546            }
21547            self.generate_expression(name)?;
21548            self.write(", ");
21549            self.generate_expression(value)?;
21550        }
21551        self.write(")");
21552        Ok(())
21553    }
21554
21555    // Map function generators
21556
21557    fn generate_map_constructor(&mut self, f: &MapConstructor) -> Result<()> {
21558        if f.curly_brace_syntax {
21559            // Curly brace syntax: MAP {'a': 1, 'b': 2} or just {'a': 1, 'b': 2}
21560            if f.with_map_keyword {
21561                self.write_keyword("MAP");
21562                self.write(" ");
21563            }
21564            self.write("{");
21565            for (i, (key, val)) in f.keys.iter().zip(f.values.iter()).enumerate() {
21566                if i > 0 {
21567                    self.write(", ");
21568                }
21569                self.generate_expression(key)?;
21570                self.write(": ");
21571                self.generate_expression(val)?;
21572            }
21573            self.write("}");
21574        } else {
21575            // MAP function syntax: MAP(ARRAY[keys], ARRAY[values])
21576            self.write_keyword("MAP");
21577            self.write("(");
21578            self.write_keyword("ARRAY");
21579            self.write("[");
21580            for (i, key) in f.keys.iter().enumerate() {
21581                if i > 0 {
21582                    self.write(", ");
21583                }
21584                self.generate_expression(key)?;
21585            }
21586            self.write("], ");
21587            self.write_keyword("ARRAY");
21588            self.write("[");
21589            for (i, val) in f.values.iter().enumerate() {
21590                if i > 0 {
21591                    self.write(", ");
21592                }
21593                self.generate_expression(val)?;
21594            }
21595            self.write("])");
21596        }
21597        Ok(())
21598    }
21599
21600    fn generate_transform_func(&mut self, name: &str, f: &TransformFunc) -> Result<()> {
21601        self.write_keyword(name);
21602        self.write("(");
21603        self.generate_expression(&f.this)?;
21604        self.write(", ");
21605        self.generate_expression(&f.transform)?;
21606        self.write(")");
21607        Ok(())
21608    }
21609
21610    // JSON function generators
21611
21612    fn generate_json_extract(&mut self, name: &str, f: &JsonExtractFunc) -> Result<()> {
21613        use crate::dialects::DialectType;
21614
21615        // Check if we should use arrow syntax (-> or ->>)
21616        let use_arrow = f.arrow_syntax && self.dialect_supports_json_arrow();
21617
21618        if use_arrow {
21619            // Output arrow syntax: expr -> path or expr ->> path
21620            self.generate_expression(&f.this)?;
21621            if name == "JSON_EXTRACT_SCALAR" || name == "JSON_EXTRACT_PATH_TEXT" {
21622                self.write(" ->> ");
21623            } else {
21624                self.write(" -> ");
21625            }
21626            self.generate_expression(&f.path)?;
21627            return Ok(());
21628        }
21629
21630        // PostgreSQL uses #>> operator for JSONB path text extraction (only when hash_arrow_syntax is true)
21631        if f.hash_arrow_syntax
21632            && matches!(
21633                self.config.dialect,
21634                Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21635            )
21636        {
21637            self.generate_expression(&f.this)?;
21638            self.write(" #>> ");
21639            self.generate_expression(&f.path)?;
21640            return Ok(());
21641        }
21642
21643        // For PostgreSQL/Redshift, use JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT for extraction without arrow syntax
21644        // Redshift maps everything to JSON_EXTRACT_PATH_TEXT since it doesn't have JSON_EXTRACT_PATH
21645        let func_name = if matches!(self.config.dialect, Some(DialectType::Redshift)) {
21646            match name {
21647                "JSON_EXTRACT_SCALAR"
21648                | "JSON_EXTRACT_PATH_TEXT"
21649                | "JSON_EXTRACT"
21650                | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH_TEXT",
21651                _ => name,
21652            }
21653        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
21654            match name {
21655                "JSON_EXTRACT_SCALAR" | "JSON_EXTRACT_PATH_TEXT" => "JSON_EXTRACT_PATH_TEXT",
21656                "JSON_EXTRACT" | "JSON_EXTRACT_PATH" => "JSON_EXTRACT_PATH",
21657                _ => name,
21658            }
21659        } else {
21660            name
21661        };
21662
21663        self.write_keyword(func_name);
21664        self.write("(");
21665        // For Redshift, strip CAST(... AS JSON) wrapper from the expression
21666        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
21667            if let Expression::Cast(ref cast) = f.this {
21668                if matches!(cast.to, crate::expressions::DataType::Json) {
21669                    self.generate_expression(&cast.this)?;
21670                } else {
21671                    self.generate_expression(&f.this)?;
21672                }
21673            } else {
21674                self.generate_expression(&f.this)?;
21675            }
21676        } else {
21677            self.generate_expression(&f.this)?;
21678        }
21679        // For PostgreSQL/Redshift JSON_EXTRACT_PATH/JSON_EXTRACT_PATH_TEXT,
21680        // decompose JSON path into separate string arguments
21681        if matches!(
21682            self.config.dialect,
21683            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21684        ) && (func_name == "JSON_EXTRACT_PATH" || func_name == "JSON_EXTRACT_PATH_TEXT")
21685        {
21686            if let Expression::Literal(ref lit) = f.path {
21687                if let Literal::String(ref s) = lit.as_ref() {
21688                    let parts = Self::decompose_json_path(s);
21689                    for part in &parts {
21690                        self.write(", '");
21691                        self.write(part);
21692                        self.write("'");
21693                    }
21694                }
21695            } else {
21696                self.write(", ");
21697                self.generate_expression(&f.path)?;
21698            }
21699        } else {
21700            self.write(", ");
21701            self.generate_expression(&f.path)?;
21702        }
21703
21704        // Output JSON_QUERY/JSON_VALUE options (Trino/Presto style)
21705        // These go BEFORE the closing parenthesis
21706        if let Some(ref wrapper) = f.wrapper_option {
21707            self.write_space();
21708            self.write_keyword(wrapper);
21709        }
21710        if let Some(ref quotes) = f.quotes_option {
21711            self.write_space();
21712            self.write_keyword(quotes);
21713            if f.on_scalar_string {
21714                self.write_space();
21715                self.write_keyword("ON SCALAR STRING");
21716            }
21717        }
21718        if let Some(ref on_err) = f.on_error {
21719            self.write_space();
21720            self.write_keyword(on_err);
21721        }
21722        if let Some(ref ret_type) = f.returning {
21723            self.write_space();
21724            self.write_keyword("RETURNING");
21725            self.write_space();
21726            self.generate_data_type(ret_type)?;
21727        }
21728
21729        self.write(")");
21730        Ok(())
21731    }
21732
21733    /// Check if the current dialect supports JSON arrow operators (-> and ->>)
21734    fn dialect_supports_json_arrow(&self) -> bool {
21735        use crate::dialects::DialectType;
21736        match self.config.dialect {
21737            // PostgreSQL, MySQL, DuckDB support -> and ->> operators
21738            Some(DialectType::PostgreSQL) => true,
21739            Some(DialectType::MySQL) => true,
21740            Some(DialectType::DuckDB) => true,
21741            Some(DialectType::CockroachDB) => true,
21742            Some(DialectType::StarRocks) => true,
21743            Some(DialectType::SQLite) => true,
21744            // Other dialects use function syntax
21745            _ => false,
21746        }
21747    }
21748
21749    fn generate_json_path(&mut self, name: &str, f: &JsonPathFunc) -> Result<()> {
21750        use crate::dialects::DialectType;
21751
21752        // PostgreSQL uses #> operator for JSONB path extraction
21753        if matches!(
21754            self.config.dialect,
21755            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
21756        ) && name == "JSON_EXTRACT_PATH"
21757        {
21758            self.generate_expression(&f.this)?;
21759            self.write(" #> ");
21760            if f.paths.len() == 1 {
21761                self.generate_expression(&f.paths[0])?;
21762            } else {
21763                // Multiple paths: ARRAY[path1, path2, ...]
21764                self.write_keyword("ARRAY");
21765                self.write("[");
21766                for (i, path) in f.paths.iter().enumerate() {
21767                    if i > 0 {
21768                        self.write(", ");
21769                    }
21770                    self.generate_expression(path)?;
21771                }
21772                self.write("]");
21773            }
21774            return Ok(());
21775        }
21776
21777        self.write_keyword(name);
21778        self.write("(");
21779        self.generate_expression(&f.this)?;
21780        for path in &f.paths {
21781            self.write(", ");
21782            self.generate_expression(path)?;
21783        }
21784        self.write(")");
21785        Ok(())
21786    }
21787
21788    fn generate_json_object(&mut self, f: &JsonObjectFunc) -> Result<()> {
21789        use crate::dialects::DialectType;
21790
21791        self.write_keyword("JSON_OBJECT");
21792        self.write("(");
21793        if f.star {
21794            self.write("*");
21795        } else {
21796            // BigQuery, MySQL, and SQLite use comma syntax: JSON_OBJECT('key', value)
21797            // Standard SQL uses colon syntax: JSON_OBJECT('key': value)
21798            // Also respect the json_key_value_pair_sep config
21799            let use_comma_syntax = self.config.json_key_value_pair_sep == ","
21800                || matches!(
21801                    self.config.dialect,
21802                    Some(DialectType::BigQuery)
21803                        | Some(DialectType::MySQL)
21804                        | Some(DialectType::SQLite)
21805                );
21806
21807            for (i, (key, value)) in f.pairs.iter().enumerate() {
21808                if i > 0 {
21809                    self.write(", ");
21810                }
21811                self.generate_expression(key)?;
21812                if use_comma_syntax {
21813                    self.write(", ");
21814                } else {
21815                    self.write(": ");
21816                }
21817                self.generate_expression(value)?;
21818            }
21819        }
21820        if let Some(null_handling) = f.null_handling {
21821            self.write_space();
21822            match null_handling {
21823                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21824                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21825            }
21826        }
21827        if f.with_unique_keys {
21828            self.write_space();
21829            self.write_keyword("WITH UNIQUE KEYS");
21830        }
21831        if let Some(ref ret_type) = f.returning_type {
21832            self.write_space();
21833            self.write_keyword("RETURNING");
21834            self.write_space();
21835            self.generate_data_type(ret_type)?;
21836            if f.format_json {
21837                self.write_space();
21838                self.write_keyword("FORMAT JSON");
21839            }
21840            if let Some(ref enc) = f.encoding {
21841                self.write_space();
21842                self.write_keyword("ENCODING");
21843                self.write_space();
21844                self.write(enc);
21845            }
21846        }
21847        self.write(")");
21848        Ok(())
21849    }
21850
21851    fn generate_json_modify(&mut self, name: &str, f: &JsonModifyFunc) -> Result<()> {
21852        self.write_keyword(name);
21853        self.write("(");
21854        self.generate_expression(&f.this)?;
21855        for (path, value) in &f.path_values {
21856            self.write(", ");
21857            self.generate_expression(path)?;
21858            self.write(", ");
21859            self.generate_expression(value)?;
21860        }
21861        self.write(")");
21862        Ok(())
21863    }
21864
21865    fn generate_json_array_agg(&mut self, f: &JsonArrayAggFunc) -> Result<()> {
21866        self.write_keyword("JSON_ARRAYAGG");
21867        self.write("(");
21868        self.generate_expression(&f.this)?;
21869        if let Some(ref order_by) = f.order_by {
21870            self.write_space();
21871            self.write_keyword("ORDER BY");
21872            self.write_space();
21873            for (i, ord) in order_by.iter().enumerate() {
21874                if i > 0 {
21875                    self.write(", ");
21876                }
21877                self.generate_ordered(ord)?;
21878            }
21879        }
21880        if let Some(null_handling) = f.null_handling {
21881            self.write_space();
21882            match null_handling {
21883                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21884                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21885            }
21886        }
21887        self.write(")");
21888        if let Some(ref filter) = f.filter {
21889            self.write_space();
21890            self.write_keyword("FILTER");
21891            self.write("(");
21892            self.write_keyword("WHERE");
21893            self.write_space();
21894            self.generate_expression(filter)?;
21895            self.write(")");
21896        }
21897        Ok(())
21898    }
21899
21900    fn generate_json_object_agg(&mut self, f: &JsonObjectAggFunc) -> Result<()> {
21901        self.write_keyword("JSON_OBJECTAGG");
21902        self.write("(");
21903        self.generate_expression(&f.key)?;
21904        self.write(": ");
21905        self.generate_expression(&f.value)?;
21906        if let Some(null_handling) = f.null_handling {
21907            self.write_space();
21908            match null_handling {
21909                JsonNullHandling::NullOnNull => self.write_keyword("NULL ON NULL"),
21910                JsonNullHandling::AbsentOnNull => self.write_keyword("ABSENT ON NULL"),
21911            }
21912        }
21913        self.write(")");
21914        if let Some(ref filter) = f.filter {
21915            self.write_space();
21916            self.write_keyword("FILTER");
21917            self.write("(");
21918            self.write_keyword("WHERE");
21919            self.write_space();
21920            self.generate_expression(filter)?;
21921            self.write(")");
21922        }
21923        Ok(())
21924    }
21925
21926    // Type casting/conversion generators
21927
21928    fn generate_convert(&mut self, f: &ConvertFunc) -> Result<()> {
21929        use crate::dialects::DialectType;
21930
21931        // Redshift: CONVERT(type, expr) -> CAST(expr AS type)
21932        if self.config.dialect == Some(DialectType::Redshift) {
21933            self.write_keyword("CAST");
21934            self.write("(");
21935            self.generate_expression(&f.this)?;
21936            self.write_space();
21937            self.write_keyword("AS");
21938            self.write_space();
21939            self.generate_data_type(&f.to)?;
21940            self.write(")");
21941            return Ok(());
21942        }
21943
21944        self.write_keyword("CONVERT");
21945        self.write("(");
21946        self.generate_data_type(&f.to)?;
21947        self.write(", ");
21948        self.generate_expression(&f.this)?;
21949        if let Some(ref style) = f.style {
21950            self.write(", ");
21951            self.generate_expression(style)?;
21952        }
21953        self.write(")");
21954        Ok(())
21955    }
21956
21957    // Additional expression generators
21958
21959    fn generate_lambda(&mut self, f: &LambdaExpr) -> Result<()> {
21960        if f.colon {
21961            // DuckDB syntax: LAMBDA x : expr
21962            self.write_keyword("LAMBDA");
21963            self.write_space();
21964            for (i, param) in f.parameters.iter().enumerate() {
21965                if i > 0 {
21966                    self.write(", ");
21967                }
21968                self.generate_identifier(param)?;
21969            }
21970            self.write(" : ");
21971        } else {
21972            // Standard syntax: x -> expr or (x, y) -> expr
21973            if f.parameters.len() == 1 {
21974                self.generate_identifier(&f.parameters[0])?;
21975            } else {
21976                self.write("(");
21977                for (i, param) in f.parameters.iter().enumerate() {
21978                    if i > 0 {
21979                        self.write(", ");
21980                    }
21981                    self.generate_identifier(param)?;
21982                }
21983                self.write(")");
21984            }
21985            self.write(" -> ");
21986        }
21987        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
21988            if let Expression::Lambda(inner) = &f.body {
21989                self.generate_lambda_with_parenthesized_single_param(inner)?;
21990                return Ok(());
21991            }
21992        }
21993
21994        self.generate_expression(&f.body)
21995    }
21996
21997    fn generate_lambda_with_parenthesized_single_param(&mut self, f: &LambdaExpr) -> Result<()> {
21998        if f.colon {
21999            return self.generate_lambda(f);
22000        }
22001
22002        self.write("(");
22003        for (i, param) in f.parameters.iter().enumerate() {
22004            if i > 0 {
22005                self.write(", ");
22006            }
22007            self.generate_identifier(param)?;
22008        }
22009        self.write(") -> ");
22010        self.generate_expression(&f.body)
22011    }
22012
22013    fn generate_named_argument(&mut self, f: &NamedArgument) -> Result<()> {
22014        self.generate_identifier(&f.name)?;
22015        match f.separator {
22016            NamedArgSeparator::DArrow => self.write(" => "),
22017            NamedArgSeparator::ColonEq => self.write(" := "),
22018            NamedArgSeparator::Eq => self.write(" = "),
22019        }
22020        self.generate_expression(&f.value)
22021    }
22022
22023    fn generate_table_argument(&mut self, f: &TableArgument) -> Result<()> {
22024        self.write_keyword(&f.prefix);
22025        self.write(" ");
22026        self.generate_expression(&f.this)
22027    }
22028
22029    fn generate_parameter(&mut self, f: &Parameter) -> Result<()> {
22030        match f.style {
22031            ParameterStyle::Question => self.write("?"),
22032            ParameterStyle::Dollar => {
22033                self.write("$");
22034                if let Some(idx) = f.index {
22035                    self.write(&idx.to_string());
22036                } else if let Some(ref name) = f.name {
22037                    // Session variable like $x or $query_id
22038                    self.write(name);
22039                }
22040            }
22041            ParameterStyle::DollarBrace => {
22042                // Template variable like ${x} or ${hiveconf:name} (Databricks, Hive)
22043                self.write("${");
22044                if let Some(ref name) = f.name {
22045                    self.write(name);
22046                }
22047                if let Some(ref expr) = f.expression {
22048                    self.write(":");
22049                    self.write(expr);
22050                }
22051                self.write("}");
22052            }
22053            ParameterStyle::Colon => {
22054                self.write(":");
22055                if let Some(idx) = f.index {
22056                    self.write(&idx.to_string());
22057                } else if let Some(ref name) = f.name {
22058                    self.write(name);
22059                }
22060            }
22061            ParameterStyle::At => {
22062                self.write("@");
22063                if let Some(ref name) = f.name {
22064                    if f.string_quoted {
22065                        self.write("'");
22066                        self.write(name);
22067                        self.write("'");
22068                    } else if f.quoted {
22069                        self.write("\"");
22070                        self.write(name);
22071                        self.write("\"");
22072                    } else {
22073                        self.write(name);
22074                    }
22075                }
22076            }
22077            ParameterStyle::DoubleAt => {
22078                self.write("@@");
22079                if let Some(ref name) = f.name {
22080                    self.write(name);
22081                }
22082            }
22083            ParameterStyle::DoubleDollar => {
22084                self.write("$$");
22085                if let Some(ref name) = f.name {
22086                    self.write(name);
22087                }
22088            }
22089            ParameterStyle::Percent => {
22090                if let Some(ref name) = f.name {
22091                    // %(name)s format
22092                    self.write("%(");
22093                    self.write(name);
22094                    self.write(")s");
22095                } else {
22096                    // %s format
22097                    self.write("%s");
22098                }
22099            }
22100            ParameterStyle::Brace => {
22101                // Spark/Databricks widget template variable: {name}
22102                // ClickHouse query parameter may include kind: {name: Type}
22103                self.write("{");
22104                if let Some(ref name) = f.name {
22105                    self.write(name);
22106                }
22107                if let Some(ref expr) = f.expression {
22108                    self.write(": ");
22109                    self.write(expr);
22110                }
22111                self.write("}");
22112            }
22113        }
22114        Ok(())
22115    }
22116
22117    fn generate_placeholder(&mut self, f: &Placeholder) -> Result<()> {
22118        self.write("?");
22119        if let Some(idx) = f.index {
22120            self.write(&idx.to_string());
22121        }
22122        Ok(())
22123    }
22124
22125    fn generate_sql_comment(&mut self, f: &SqlComment) -> Result<()> {
22126        if f.is_block {
22127            self.write("/*");
22128            self.write(&f.text);
22129            self.write("*/");
22130        } else {
22131            self.write("--");
22132            self.write(&f.text);
22133        }
22134        Ok(())
22135    }
22136
22137    // Additional predicate generators
22138
22139    fn generate_similar_to(&mut self, f: &SimilarToExpr) -> Result<()> {
22140        self.generate_expression(&f.this)?;
22141        if f.not {
22142            self.write_space();
22143            self.write_keyword("NOT");
22144        }
22145        self.write_space();
22146        self.write_keyword("SIMILAR TO");
22147        self.write_space();
22148        self.generate_expression(&f.pattern)?;
22149        if let Some(ref escape) = f.escape {
22150            self.write_space();
22151            self.write_keyword("ESCAPE");
22152            self.write_space();
22153            self.generate_expression(escape)?;
22154        }
22155        Ok(())
22156    }
22157
22158    fn generate_quantified(&mut self, name: &str, f: &QuantifiedExpr) -> Result<()> {
22159        self.generate_expression(&f.this)?;
22160        self.write_space();
22161        // Output comparison operator if present
22162        if let Some(op) = &f.op {
22163            match op {
22164                QuantifiedOp::Eq => self.write("="),
22165                QuantifiedOp::Neq => self.write("<>"),
22166                QuantifiedOp::Lt => self.write("<"),
22167                QuantifiedOp::Lte => self.write("<="),
22168                QuantifiedOp::Gt => self.write(">"),
22169                QuantifiedOp::Gte => self.write(">="),
22170            }
22171            self.write_space();
22172        }
22173        self.write_keyword(name);
22174
22175        // If the child is a Subquery, it provides its own parens — output with space
22176        if matches!(&f.subquery, Expression::Subquery(_)) {
22177            self.write_space();
22178            self.generate_expression(&f.subquery)?;
22179        } else {
22180            let is_statement = matches!(
22181                &f.subquery,
22182                Expression::Select(_)
22183                    | Expression::Union(_)
22184                    | Expression::Intersect(_)
22185                    | Expression::Except(_)
22186            );
22187            if is_statement
22188                && !self.config.quantified_no_paren_space
22189                && matches!(self.config.dialect, Some(DialectType::ClickHouse))
22190            {
22191                self.write_space();
22192            }
22193            self.write("(");
22194
22195            if self.config.pretty && is_statement {
22196                self.write_newline();
22197                self.indent_level += 1;
22198                self.write_indent();
22199            }
22200            self.generate_expression(&f.subquery)?;
22201            if self.config.pretty && is_statement {
22202                self.write_newline();
22203                self.indent_level -= 1;
22204                self.write_indent();
22205            }
22206            self.write(")");
22207        }
22208        Ok(())
22209    }
22210
22211    fn generate_overlaps(&mut self, f: &OverlapsExpr) -> Result<()> {
22212        // Check if this is a simple binary form (this OVERLAPS expression)
22213        if let (Some(this), Some(expr)) = (&f.this, &f.expression) {
22214            self.generate_expression(this)?;
22215            self.write_space();
22216            self.write_keyword("OVERLAPS");
22217            self.write_space();
22218            self.generate_expression(expr)?;
22219        } else if let (Some(ls), Some(le), Some(rs), Some(re)) =
22220            (&f.left_start, &f.left_end, &f.right_start, &f.right_end)
22221        {
22222            // Full ANSI form: (a, b) OVERLAPS (c, d)
22223            self.write("(");
22224            self.generate_expression(ls)?;
22225            self.write(", ");
22226            self.generate_expression(le)?;
22227            self.write(")");
22228            self.write_space();
22229            self.write_keyword("OVERLAPS");
22230            self.write_space();
22231            self.write("(");
22232            self.generate_expression(rs)?;
22233            self.write(", ");
22234            self.generate_expression(re)?;
22235            self.write(")");
22236        }
22237        Ok(())
22238    }
22239
22240    // Type conversion generators
22241
22242    fn generate_try_cast(&mut self, cast: &Cast) -> Result<()> {
22243        use crate::dialects::DialectType;
22244
22245        // SingleStore uses !:> syntax for try cast
22246        if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
22247            self.generate_expression(&cast.this)?;
22248            self.write(" !:> ");
22249            self.generate_data_type(&cast.to)?;
22250            return Ok(());
22251        }
22252
22253        // Teradata uses TRYCAST (no underscore)
22254        if matches!(self.config.dialect, Some(DialectType::Teradata)) {
22255            self.write_keyword("TRYCAST");
22256            self.write("(");
22257            self.generate_expression(&cast.this)?;
22258            self.write_space();
22259            self.write_keyword("AS");
22260            self.write_space();
22261            self.generate_data_type(&cast.to)?;
22262            self.write(")");
22263            return Ok(());
22264        }
22265
22266        // Dialects without TRY_CAST: generate as regular CAST
22267        let keyword = if matches!(
22268            self.config.dialect,
22269            Some(DialectType::Hive)
22270                | Some(DialectType::MySQL)
22271                | Some(DialectType::SQLite)
22272                | Some(DialectType::Oracle)
22273                | Some(DialectType::ClickHouse)
22274                | Some(DialectType::Redshift)
22275                | Some(DialectType::PostgreSQL)
22276                | Some(DialectType::StarRocks)
22277                | Some(DialectType::Doris)
22278        ) {
22279            "CAST"
22280        } else {
22281            "TRY_CAST"
22282        };
22283
22284        self.write_keyword(keyword);
22285        self.write("(");
22286        self.generate_expression(&cast.this)?;
22287        self.write_space();
22288        self.write_keyword("AS");
22289        self.write_space();
22290        self.generate_data_type(&cast.to)?;
22291
22292        // Output FORMAT clause if present
22293        if let Some(format) = &cast.format {
22294            self.write_space();
22295            self.write_keyword("FORMAT");
22296            self.write_space();
22297            self.generate_expression(format)?;
22298        }
22299
22300        self.write(")");
22301        Ok(())
22302    }
22303
22304    fn generate_safe_cast(&mut self, cast: &Cast) -> Result<()> {
22305        self.write_keyword("SAFE_CAST");
22306        self.write("(");
22307        self.generate_expression(&cast.this)?;
22308        self.write_space();
22309        self.write_keyword("AS");
22310        self.write_space();
22311        self.generate_data_type(&cast.to)?;
22312
22313        // Output FORMAT clause if present
22314        if let Some(format) = &cast.format {
22315            self.write_space();
22316            self.write_keyword("FORMAT");
22317            self.write_space();
22318            self.generate_expression(format)?;
22319        }
22320
22321        self.write(")");
22322        Ok(())
22323    }
22324
22325    // Array/struct/map access generators
22326
22327    fn generate_subscript(&mut self, s: &Subscript) -> Result<()> {
22328        // Wrap the base expression in parentheses when it uses arrow syntax (->)
22329        // which has lower precedence than bracket subscript ([]).
22330        // E.g., (t.v -> '$.a')[s.x] instead of t.v -> '$.a'[s.x]
22331        let needs_parens = matches!(&s.this, Expression::JsonExtract(ref f) if f.arrow_syntax);
22332        if needs_parens {
22333            self.write("(");
22334        }
22335        self.generate_expression(&s.this)?;
22336        if needs_parens {
22337            self.write(")");
22338        }
22339        self.write("[");
22340        self.generate_expression(&s.index)?;
22341        self.write("]");
22342        Ok(())
22343    }
22344
22345    fn generate_dot_access(&mut self, d: &DotAccess) -> Result<()> {
22346        self.generate_expression(&d.this)?;
22347        // Snowflake uses : (colon) for first-level struct/object field access on CAST/column expressions
22348        // e.g., CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2
22349        let use_colon = matches!(self.config.dialect, Some(DialectType::Snowflake))
22350            && matches!(
22351                &d.this,
22352                Expression::Cast(_) | Expression::SafeCast(_) | Expression::TryCast(_)
22353            );
22354        if use_colon {
22355            self.write(":");
22356        } else {
22357            self.write(".");
22358        }
22359        self.generate_identifier(&d.field)
22360    }
22361
22362    fn generate_method_call(&mut self, m: &MethodCall) -> Result<()> {
22363        self.generate_expression(&m.this)?;
22364        self.write(".");
22365        // Method names after a dot should not be quoted based on reserved keywords
22366        // Only quote if explicitly marked as quoted in the AST
22367        if m.method.quoted {
22368            let q = self.config.identifier_quote;
22369            self.write(&format!("{}{}{}", q, m.method.name, q));
22370        } else {
22371            self.write(&m.method.name);
22372        }
22373        self.write("(");
22374        for (i, arg) in m.args.iter().enumerate() {
22375            if i > 0 {
22376                self.write(", ");
22377            }
22378            self.generate_expression(arg)?;
22379        }
22380        self.write(")");
22381        Ok(())
22382    }
22383
22384    fn generate_array_slice(&mut self, s: &ArraySlice) -> Result<()> {
22385        // Check if we need to wrap the inner expression in parentheses
22386        // JSON arrow expressions have lower precedence than array subscript
22387        let needs_parens = matches!(
22388            &s.this,
22389            Expression::JsonExtract(f) if f.arrow_syntax
22390        ) || matches!(
22391            &s.this,
22392            Expression::JsonExtractScalar(f) if f.arrow_syntax
22393        );
22394
22395        if needs_parens {
22396            self.write("(");
22397        }
22398        self.generate_expression(&s.this)?;
22399        if needs_parens {
22400            self.write(")");
22401        }
22402        self.write("[");
22403        if let Some(start) = &s.start {
22404            self.generate_expression(start)?;
22405        }
22406        self.write(":");
22407        if let Some(end) = &s.end {
22408            self.generate_expression(end)?;
22409        }
22410        self.write("]");
22411        Ok(())
22412    }
22413
22414    fn generate_binary_op(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22415        // Generate left expression, but skip trailing comments if they're already in left_comments
22416        // to avoid duplication (comments are captured as both expr.trailing_comments
22417        // and BinaryOp.left_comments during parsing)
22418        match &op.left {
22419            Expression::Column(col) => {
22420                // Generate column with trailing comments but skip them if they're
22421                // already captured in BinaryOp.left_comments to avoid duplication
22422                if let Some(table) = &col.table {
22423                    self.generate_identifier(table)?;
22424                    self.write(".");
22425                }
22426                self.generate_identifier(&col.name)?;
22427                // Oracle-style join marker (+)
22428                if col.join_mark && self.config.supports_column_join_marks {
22429                    self.write(" (+)");
22430                }
22431                // Output column trailing comments if they're not already in left_comments
22432                if op.left_comments.is_empty() {
22433                    for comment in &col.trailing_comments {
22434                        self.write_space();
22435                        self.write_formatted_comment(comment);
22436                    }
22437                }
22438            }
22439            Expression::Add(inner_op)
22440            | Expression::Sub(inner_op)
22441            | Expression::Mul(inner_op)
22442            | Expression::Div(inner_op)
22443            | Expression::Concat(inner_op) => {
22444                // Generate binary op without its trailing comments
22445                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22446                    Expression::Add(_) => "+",
22447                    Expression::Sub(_) => "-",
22448                    Expression::Mul(_) => "*",
22449                    Expression::Div(_) => "/",
22450                    Expression::Concat(_) => "||",
22451                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22452                })?;
22453            }
22454            _ => {
22455                self.generate_expression(&op.left)?;
22456            }
22457        }
22458        // Output comments after left operand
22459        for comment in &op.left_comments {
22460            self.write_space();
22461            self.write_formatted_comment(comment);
22462        }
22463        if self.config.pretty
22464            && matches!(self.config.dialect, Some(DialectType::Snowflake))
22465            && (operator == "AND" || operator == "OR")
22466        {
22467            self.write_newline();
22468            self.write_indent();
22469            self.write_keyword(operator);
22470        } else {
22471            self.write_space();
22472            if operator.chars().all(|c| c.is_alphabetic()) {
22473                self.write_keyword(operator);
22474            } else {
22475                self.write(operator);
22476            }
22477        }
22478        // Output comments after operator (before right operand)
22479        for comment in &op.operator_comments {
22480            self.write_space();
22481            self.write_formatted_comment(comment);
22482        }
22483        self.write_space();
22484        self.generate_expression(&op.right)?;
22485        // Output trailing comments after right operand
22486        for comment in &op.trailing_comments {
22487            self.write_space();
22488            self.write_formatted_comment(comment);
22489        }
22490        Ok(())
22491    }
22492
22493    fn generate_connector_op(&mut self, op: &BinaryOp, connector: ConnectorOperator) -> Result<()> {
22494        let keyword = connector.keyword();
22495        let Some(terms) = self.flatten_connector_terms(op, connector) else {
22496            return self.generate_binary_op(op, keyword);
22497        };
22498
22499        let wrap_clickhouse_or_term = |generator: &mut Self, term: &Expression| -> Result<()> {
22500            let should_wrap = matches!(connector, ConnectorOperator::Or)
22501                && matches!(generator.config.dialect, Some(DialectType::ClickHouse))
22502                && matches!(
22503                    generator.config.source_dialect,
22504                    Some(DialectType::ClickHouse)
22505                )
22506                && matches!(term, Expression::And(_));
22507            if should_wrap {
22508                generator.write("(");
22509                generator.generate_expression(term)?;
22510                generator.write(")");
22511            } else {
22512                generator.generate_expression(term)?;
22513            }
22514            Ok(())
22515        };
22516
22517        wrap_clickhouse_or_term(self, terms[0])?;
22518        for term in terms.iter().skip(1) {
22519            if self.config.pretty && matches!(self.config.dialect, Some(DialectType::Snowflake)) {
22520                self.write_newline();
22521                self.write_indent();
22522                self.write_keyword(keyword);
22523            } else {
22524                self.write_space();
22525                self.write_keyword(keyword);
22526            }
22527            self.write_space();
22528            wrap_clickhouse_or_term(self, term)?;
22529        }
22530
22531        Ok(())
22532    }
22533
22534    fn flatten_connector_terms<'a>(
22535        &self,
22536        root: &'a BinaryOp,
22537        connector: ConnectorOperator,
22538    ) -> Option<Vec<&'a Expression>> {
22539        if !root.left_comments.is_empty()
22540            || !root.operator_comments.is_empty()
22541            || !root.trailing_comments.is_empty()
22542        {
22543            return None;
22544        }
22545
22546        let mut terms = Vec::new();
22547        let mut stack: Vec<&Expression> = vec![&root.right, &root.left];
22548
22549        while let Some(expr) = stack.pop() {
22550            match (connector, expr) {
22551                (ConnectorOperator::And, Expression::And(inner))
22552                    if inner.left_comments.is_empty()
22553                        && inner.operator_comments.is_empty()
22554                        && inner.trailing_comments.is_empty() =>
22555                {
22556                    stack.push(&inner.right);
22557                    stack.push(&inner.left);
22558                }
22559                (ConnectorOperator::Or, Expression::Or(inner))
22560                    if inner.left_comments.is_empty()
22561                        && inner.operator_comments.is_empty()
22562                        && inner.trailing_comments.is_empty() =>
22563                {
22564                    stack.push(&inner.right);
22565                    stack.push(&inner.left);
22566                }
22567                _ => terms.push(expr),
22568            }
22569        }
22570
22571        if terms.len() > 1 {
22572            Some(terms)
22573        } else {
22574            None
22575        }
22576    }
22577
22578    /// Generate LIKE/ILIKE operation with optional ESCAPE clause
22579    fn generate_like_op(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
22580        self.generate_like_op_inner(op, operator, false)
22581    }
22582
22583    fn generate_like_op_negated(&mut self, op: &LikeOp, operator: &str) -> Result<()> {
22584        self.generate_like_op_inner(op, operator, true)
22585    }
22586
22587    fn generate_like_op_inner(&mut self, op: &LikeOp, operator: &str, negated: bool) -> Result<()> {
22588        if negated
22589            && matches!(
22590                self.config.dialect,
22591                Some(DialectType::ClickHouse)
22592                    | Some(DialectType::DataFusion)
22593                    | Some(DialectType::TSQL)
22594                    | Some(DialectType::Fabric)
22595            )
22596        {
22597            self.write_keyword("NOT");
22598            self.write_space();
22599            return self.generate_like_op_inner(op, operator, false);
22600        }
22601
22602        self.generate_expression(&op.left)?;
22603        self.write_space();
22604        if negated {
22605            self.write_keyword("NOT");
22606            self.write_space();
22607        }
22608        // Drill backtick-quotes ILIKE
22609        if operator == "ILIKE" && matches!(self.config.dialect, Some(DialectType::Drill)) {
22610            self.write("`ILIKE`");
22611        } else {
22612            self.write_keyword(operator);
22613        }
22614        if let Some(quantifier) = &op.quantifier {
22615            self.write_space();
22616            self.write_keyword(quantifier);
22617            // Match Python sqlglot behavior:
22618            // ANY + Paren (single value): no space → ILIKE ANY('%a%')
22619            // ANY + Tuple (multiple values): space → LIKE ANY ('a', 'b')
22620            // ALL + anything: always space → LIKE ALL ('%a%'), LIKE ALL ('a', 'b')
22621            let is_any =
22622                quantifier.eq_ignore_ascii_case("ANY") || quantifier.eq_ignore_ascii_case("SOME");
22623            if !(is_any && matches!(&op.right, Expression::Paren(_))) {
22624                self.write_space();
22625            }
22626        } else {
22627            self.write_space();
22628        }
22629        self.generate_expression(&op.right)?;
22630        if let Some(escape) = &op.escape {
22631            self.write_space();
22632            self.write_keyword("ESCAPE");
22633            self.write_space();
22634            self.generate_expression(escape)?;
22635        }
22636        Ok(())
22637    }
22638
22639    /// Generate null-safe equality
22640    /// MySQL uses <=>, other dialects use IS NOT DISTINCT FROM
22641    fn generate_null_safe_eq(&mut self, op: &BinaryOp) -> Result<()> {
22642        use crate::dialects::DialectType;
22643        self.generate_expression(&op.left)?;
22644        self.write_space();
22645        if matches!(self.config.dialect, Some(DialectType::MySQL)) {
22646            self.write("<=>");
22647        } else {
22648            self.write_keyword("IS NOT DISTINCT FROM");
22649        }
22650        self.write_space();
22651        self.generate_expression(&op.right)?;
22652        Ok(())
22653    }
22654
22655    /// Generate IS DISTINCT FROM (null-safe inequality)
22656    fn generate_null_safe_neq(&mut self, op: &BinaryOp) -> Result<()> {
22657        self.generate_expression(&op.left)?;
22658        self.write_space();
22659        self.write_keyword("IS DISTINCT FROM");
22660        self.write_space();
22661        self.generate_expression(&op.right)?;
22662        Ok(())
22663    }
22664
22665    /// Generate binary op without trailing comments (used when nested inside another binary op)
22666    fn generate_binary_op_no_trailing(&mut self, op: &BinaryOp, operator: &str) -> Result<()> {
22667        // Generate left expression, but skip trailing comments
22668        match &op.left {
22669            Expression::Column(col) => {
22670                if let Some(table) = &col.table {
22671                    self.generate_identifier(table)?;
22672                    self.write(".");
22673                }
22674                self.generate_identifier(&col.name)?;
22675                // Oracle-style join marker (+)
22676                if col.join_mark && self.config.supports_column_join_marks {
22677                    self.write(" (+)");
22678                }
22679            }
22680            Expression::Add(inner_op)
22681            | Expression::Sub(inner_op)
22682            | Expression::Mul(inner_op)
22683            | Expression::Div(inner_op)
22684            | Expression::Concat(inner_op) => {
22685                self.generate_binary_op_no_trailing(inner_op, match &op.left {
22686                    Expression::Add(_) => "+",
22687                    Expression::Sub(_) => "-",
22688                    Expression::Mul(_) => "*",
22689                    Expression::Div(_) => "/",
22690                    Expression::Concat(_) => "||",
22691                    _ => unreachable!("op.left variant already matched by outer arm as Add/Sub/Mul/Div/Concat"),
22692                })?;
22693            }
22694            _ => {
22695                self.generate_expression(&op.left)?;
22696            }
22697        }
22698        // Output left_comments
22699        for comment in &op.left_comments {
22700            self.write_space();
22701            self.write_formatted_comment(comment);
22702        }
22703        self.write_space();
22704        if operator.chars().all(|c| c.is_alphabetic()) {
22705            self.write_keyword(operator);
22706        } else {
22707            self.write(operator);
22708        }
22709        // Output operator_comments
22710        for comment in &op.operator_comments {
22711            self.write_space();
22712            self.write_formatted_comment(comment);
22713        }
22714        self.write_space();
22715        // Generate right expression, but skip trailing comments if it's a Column
22716        // (the parent's left_comments will output them)
22717        match &op.right {
22718            Expression::Column(col) => {
22719                if let Some(table) = &col.table {
22720                    self.generate_identifier(table)?;
22721                    self.write(".");
22722                }
22723                self.generate_identifier(&col.name)?;
22724                // Oracle-style join marker (+)
22725                if col.join_mark && self.config.supports_column_join_marks {
22726                    self.write(" (+)");
22727                }
22728            }
22729            _ => {
22730                self.generate_expression(&op.right)?;
22731            }
22732        }
22733        // Skip trailing_comments - parent will handle them via its left_comments
22734        Ok(())
22735    }
22736
22737    fn generate_unary_op(&mut self, op: &UnaryOp, operator: &str) -> Result<()> {
22738        if operator.chars().all(|c| c.is_alphabetic()) {
22739            self.write_keyword(operator);
22740            self.write_space();
22741        } else {
22742            self.write(operator);
22743            // Add space between consecutive unary operators (e.g., "- -5" not "--5")
22744            if matches!(&op.this, Expression::Neg(_) | Expression::BitwiseNot(_)) {
22745                self.write_space();
22746            }
22747        }
22748        self.generate_expression(&op.this)
22749    }
22750
22751    fn generate_in(&mut self, in_expr: &In) -> Result<()> {
22752        // Generic mode supports two styles for negated IN:
22753        // - Prefix: NOT a IN (...)
22754        // - Infix:  a NOT IN (...)
22755        let is_generic =
22756            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
22757        let use_prefix_not =
22758            in_expr.not && is_generic && self.config.not_in_style == NotInStyle::Prefix;
22759        if use_prefix_not {
22760            self.write_keyword("NOT");
22761            self.write_space();
22762        }
22763        self.generate_expression(&in_expr.this)?;
22764        if in_expr.global {
22765            self.write_space();
22766            self.write_keyword("GLOBAL");
22767        }
22768        if in_expr.not && !use_prefix_not {
22769            self.write_space();
22770            self.write_keyword("NOT");
22771        }
22772        self.write_space();
22773        self.write_keyword("IN");
22774
22775        // BigQuery: IN UNNEST(expr)
22776        if let Some(unnest_expr) = &in_expr.unnest {
22777            self.write_space();
22778            self.write_keyword("UNNEST");
22779            self.write("(");
22780            self.generate_expression(unnest_expr)?;
22781            self.write(")");
22782            return Ok(());
22783        }
22784
22785        if let Some(query) = &in_expr.query {
22786            // Check if this is a bare identifier (PIVOT FOR foo IN y_enum)
22787            // vs a subquery (col IN (SELECT ...))
22788            let is_bare = in_expr.expressions.is_empty()
22789                && !matches!(
22790                    query,
22791                    Expression::Select(_)
22792                        | Expression::Union(_)
22793                        | Expression::Intersect(_)
22794                        | Expression::Except(_)
22795                        | Expression::Subquery(_)
22796                );
22797            if is_bare {
22798                // Bare identifier: no parentheses
22799                self.write_space();
22800                self.generate_expression(query)?;
22801            } else {
22802                // Subquery: with parentheses
22803                self.write(" (");
22804                let is_statement = matches!(
22805                    query,
22806                    Expression::Select(_)
22807                        | Expression::Union(_)
22808                        | Expression::Intersect(_)
22809                        | Expression::Except(_)
22810                        | Expression::Subquery(_)
22811                );
22812                if self.config.pretty && is_statement {
22813                    self.write_newline();
22814                    self.indent_level += 1;
22815                    self.write_indent();
22816                }
22817                self.generate_expression(query)?;
22818                if self.config.pretty && is_statement {
22819                    self.write_newline();
22820                    self.indent_level -= 1;
22821                    self.write_indent();
22822                }
22823                self.write(")");
22824            }
22825        } else {
22826            // DuckDB: IN without parentheses for single expression that is NOT a literal
22827            // (array/list membership like 'red' IN tbl.flags)
22828            // ClickHouse: IN without parentheses for single non-array expressions
22829            let is_duckdb = matches!(
22830                self.config.dialect,
22831                Some(crate::dialects::DialectType::DuckDB)
22832            );
22833            let is_clickhouse = matches!(
22834                self.config.dialect,
22835                Some(crate::dialects::DialectType::ClickHouse)
22836            );
22837            let single_expr = in_expr.expressions.len() == 1;
22838            if is_clickhouse && single_expr {
22839                if let Expression::Array(arr) = &in_expr.expressions[0] {
22840                    // ClickHouse: x IN [1, 2] -> x IN (1, 2)
22841                    self.write(" (");
22842                    for (i, expr) in arr.expressions.iter().enumerate() {
22843                        if i > 0 {
22844                            self.write(", ");
22845                        }
22846                        self.generate_expression(expr)?;
22847                    }
22848                    self.write(")");
22849                } else if in_expr.is_field {
22850                    self.write_space();
22851                    self.generate_expression(&in_expr.expressions[0])?;
22852                } else {
22853                    self.write(" (");
22854                    self.generate_expression(&in_expr.expressions[0])?;
22855                    self.write(")");
22856                }
22857            } else {
22858                let is_bare_ref = single_expr
22859                    && matches!(
22860                        &in_expr.expressions[0],
22861                        Expression::Column(_) | Expression::Identifier(_) | Expression::Dot(_)
22862                    );
22863                if (is_duckdb && is_bare_ref) || (in_expr.is_field && single_expr) {
22864                    // Bare field reference (no parens in source): IN identifier
22865                    // Also DuckDB: IN without parentheses for array/list membership
22866                    self.write_space();
22867                    self.generate_expression(&in_expr.expressions[0])?;
22868                } else {
22869                    // Standard IN (list)
22870                    self.write(" (");
22871                    for (i, expr) in in_expr.expressions.iter().enumerate() {
22872                        if i > 0 {
22873                            self.write(", ");
22874                        }
22875                        self.generate_expression(expr)?;
22876                    }
22877                    self.write(")");
22878                }
22879            }
22880        }
22881
22882        Ok(())
22883    }
22884
22885    fn generate_between(&mut self, between: &Between) -> Result<()> {
22886        // Generic mode: normalize NOT BETWEEN to prefix form: NOT a BETWEEN b AND c
22887        let use_prefix_not = between.not
22888            && (self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic));
22889        if use_prefix_not {
22890            self.write_keyword("NOT");
22891            self.write_space();
22892        }
22893        self.generate_expression(&between.this)?;
22894        if between.not && !use_prefix_not {
22895            self.write_space();
22896            self.write_keyword("NOT");
22897        }
22898        self.write_space();
22899        self.write_keyword("BETWEEN");
22900        // Emit SYMMETRIC/ASYMMETRIC if present
22901        if let Some(sym) = between.symmetric {
22902            if sym {
22903                self.write(" SYMMETRIC");
22904            } else {
22905                self.write(" ASYMMETRIC");
22906            }
22907        }
22908        self.write_space();
22909        self.generate_expression(&between.low)?;
22910        self.write_space();
22911        self.write_keyword("AND");
22912        self.write_space();
22913        self.generate_expression(&between.high)
22914    }
22915
22916    fn generate_is_null(&mut self, is_null: &IsNull) -> Result<()> {
22917        // Generic mode: normalize IS NOT NULL to prefix form: NOT x IS NULL
22918        let use_prefix_not = is_null.not
22919            && (self.config.dialect.is_none()
22920                || self.config.dialect == Some(DialectType::Generic)
22921                || is_null.postfix_form);
22922        if use_prefix_not {
22923            // NOT x IS NULL (generic normalization and NOTNULL postfix form)
22924            self.write_keyword("NOT");
22925            self.write_space();
22926            self.generate_expression(&is_null.this)?;
22927            self.write_space();
22928            self.write_keyword("IS");
22929            self.write_space();
22930            self.write_keyword("NULL");
22931        } else {
22932            self.generate_expression(&is_null.this)?;
22933            self.write_space();
22934            self.write_keyword("IS");
22935            if is_null.not {
22936                self.write_space();
22937                self.write_keyword("NOT");
22938            }
22939            self.write_space();
22940            self.write_keyword("NULL");
22941        }
22942        Ok(())
22943    }
22944
22945    fn generate_is_true(&mut self, is_true: &IsTrueFalse) -> Result<()> {
22946        self.generate_expression(&is_true.this)?;
22947        self.write_space();
22948        self.write_keyword("IS");
22949        if is_true.not {
22950            self.write_space();
22951            self.write_keyword("NOT");
22952        }
22953        self.write_space();
22954        self.write_keyword("TRUE");
22955        Ok(())
22956    }
22957
22958    fn generate_is_false(&mut self, is_false: &IsTrueFalse) -> Result<()> {
22959        self.generate_expression(&is_false.this)?;
22960        self.write_space();
22961        self.write_keyword("IS");
22962        if is_false.not {
22963            self.write_space();
22964            self.write_keyword("NOT");
22965        }
22966        self.write_space();
22967        self.write_keyword("FALSE");
22968        Ok(())
22969    }
22970
22971    fn generate_is_json(&mut self, is_json: &IsJson) -> Result<()> {
22972        self.generate_expression(&is_json.this)?;
22973        self.write_space();
22974        self.write_keyword("IS");
22975        if is_json.negated {
22976            self.write_space();
22977            self.write_keyword("NOT");
22978        }
22979        self.write_space();
22980        self.write_keyword("JSON");
22981
22982        // Output JSON type if specified (VALUE, SCALAR, OBJECT, ARRAY)
22983        if let Some(ref json_type) = is_json.json_type {
22984            self.write_space();
22985            self.write_keyword(json_type);
22986        }
22987
22988        // Output key uniqueness constraint if specified
22989        match &is_json.unique_keys {
22990            Some(JsonUniqueKeys::With) => {
22991                self.write_space();
22992                self.write_keyword("WITH UNIQUE KEYS");
22993            }
22994            Some(JsonUniqueKeys::Without) => {
22995                self.write_space();
22996                self.write_keyword("WITHOUT UNIQUE KEYS");
22997            }
22998            Some(JsonUniqueKeys::Shorthand) => {
22999                self.write_space();
23000                self.write_keyword("UNIQUE KEYS");
23001            }
23002            None => {}
23003        }
23004
23005        Ok(())
23006    }
23007
23008    fn generate_is(&mut self, is_expr: &BinaryOp) -> Result<()> {
23009        self.generate_expression(&is_expr.left)?;
23010        self.write_space();
23011        self.write_keyword("IS");
23012        self.write_space();
23013        self.generate_expression(&is_expr.right)
23014    }
23015
23016    fn generate_exists(&mut self, exists: &Exists) -> Result<()> {
23017        if exists.not {
23018            self.write_keyword("NOT");
23019            self.write_space();
23020        }
23021        self.write_keyword("EXISTS");
23022        self.write("(");
23023        let is_statement = matches!(
23024            &exists.this,
23025            Expression::Select(_)
23026                | Expression::Union(_)
23027                | Expression::Intersect(_)
23028                | Expression::Except(_)
23029        );
23030        if self.config.pretty && is_statement {
23031            self.write_newline();
23032            self.indent_level += 1;
23033            self.write_indent();
23034            self.generate_expression(&exists.this)?;
23035            self.write_newline();
23036            self.indent_level -= 1;
23037            self.write_indent();
23038            self.write(")");
23039        } else {
23040            self.generate_expression(&exists.this)?;
23041            self.write(")");
23042        }
23043        Ok(())
23044    }
23045
23046    fn generate_member_of(&mut self, op: &BinaryOp) -> Result<()> {
23047        self.generate_expression(&op.left)?;
23048        self.write_space();
23049        self.write_keyword("MEMBER OF");
23050        self.write("(");
23051        self.generate_expression(&op.right)?;
23052        self.write(")");
23053        Ok(())
23054    }
23055
23056    fn generate_subquery(&mut self, subquery: &Subquery) -> Result<()> {
23057        if subquery.lateral {
23058            self.write_keyword("LATERAL");
23059            self.write_space();
23060        }
23061
23062        // If the inner expression is a Paren wrapping a statement, don't add extra parentheses
23063        // This handles cases like ((SELECT 1)) LIMIT 1 where we wrap Paren in Subquery
23064        // to carry the LIMIT modifier without adding more parens
23065        let skip_outer_parens = if let Expression::Paren(ref p) = &subquery.this {
23066            matches!(
23067                &p.this,
23068                Expression::Select(_)
23069                    | Expression::Union(_)
23070                    | Expression::Intersect(_)
23071                    | Expression::Except(_)
23072                    | Expression::Subquery(_)
23073            )
23074        } else {
23075            false
23076        };
23077
23078        // Check if inner expression is a statement for pretty formatting
23079        let is_statement = matches!(
23080            &subquery.this,
23081            Expression::Select(_)
23082                | Expression::Union(_)
23083                | Expression::Intersect(_)
23084                | Expression::Except(_)
23085                | Expression::Merge(_)
23086        );
23087
23088        if !skip_outer_parens {
23089            self.write("(");
23090            if self.config.pretty && is_statement {
23091                self.write_newline();
23092                self.indent_level += 1;
23093                self.write_indent();
23094            }
23095        }
23096        self.generate_expression(&subquery.this)?;
23097
23098        // Generate ORDER BY, LIMIT, OFFSET based on modifiers_inside flag
23099        if subquery.modifiers_inside {
23100            // Generate modifiers INSIDE the parentheses: (SELECT ... LIMIT 1)
23101            if let Some(order_by) = &subquery.order_by {
23102                self.write_space();
23103                self.write_keyword("ORDER BY");
23104                self.write_space();
23105                for (i, ord) in order_by.expressions.iter().enumerate() {
23106                    if i > 0 {
23107                        self.write(", ");
23108                    }
23109                    self.generate_ordered(ord)?;
23110                }
23111            }
23112
23113            if let Some(limit) = &subquery.limit {
23114                self.write_space();
23115                self.write_keyword("LIMIT");
23116                self.write_space();
23117                self.generate_expression(&limit.this)?;
23118                if limit.percent {
23119                    self.write_space();
23120                    self.write_keyword("PERCENT");
23121                }
23122            }
23123
23124            if let Some(offset) = &subquery.offset {
23125                self.write_space();
23126                self.write_keyword("OFFSET");
23127                self.write_space();
23128                self.generate_expression(&offset.this)?;
23129            }
23130        }
23131
23132        if !skip_outer_parens {
23133            if self.config.pretty && is_statement {
23134                self.write_newline();
23135                self.indent_level -= 1;
23136                self.write_indent();
23137            }
23138            self.write(")");
23139        }
23140
23141        // Generate modifiers OUTSIDE the parentheses: (SELECT ...) LIMIT 1
23142        if !subquery.modifiers_inside {
23143            if let Some(order_by) = &subquery.order_by {
23144                self.write_space();
23145                self.write_keyword("ORDER BY");
23146                self.write_space();
23147                for (i, ord) in order_by.expressions.iter().enumerate() {
23148                    if i > 0 {
23149                        self.write(", ");
23150                    }
23151                    self.generate_ordered(ord)?;
23152                }
23153            }
23154
23155            if let Some(limit) = &subquery.limit {
23156                self.write_space();
23157                self.write_keyword("LIMIT");
23158                self.write_space();
23159                self.generate_expression(&limit.this)?;
23160                if limit.percent {
23161                    self.write_space();
23162                    self.write_keyword("PERCENT");
23163                }
23164            }
23165
23166            if let Some(offset) = &subquery.offset {
23167                self.write_space();
23168                self.write_keyword("OFFSET");
23169                self.write_space();
23170                self.generate_expression(&offset.this)?;
23171            }
23172
23173            // Generate DISTRIBUTE BY (Hive/Spark)
23174            if let Some(distribute_by) = &subquery.distribute_by {
23175                self.write_space();
23176                self.write_keyword("DISTRIBUTE BY");
23177                self.write_space();
23178                for (i, expr) in distribute_by.expressions.iter().enumerate() {
23179                    if i > 0 {
23180                        self.write(", ");
23181                    }
23182                    self.generate_expression(expr)?;
23183                }
23184            }
23185
23186            // Generate SORT BY (Hive/Spark)
23187            if let Some(sort_by) = &subquery.sort_by {
23188                self.write_space();
23189                self.write_keyword("SORT BY");
23190                self.write_space();
23191                for (i, ord) in sort_by.expressions.iter().enumerate() {
23192                    if i > 0 {
23193                        self.write(", ");
23194                    }
23195                    self.generate_ordered(ord)?;
23196                }
23197            }
23198
23199            // Generate CLUSTER BY (Hive/Spark)
23200            if let Some(cluster_by) = &subquery.cluster_by {
23201                self.write_space();
23202                self.write_keyword("CLUSTER BY");
23203                self.write_space();
23204                for (i, ord) in cluster_by.expressions.iter().enumerate() {
23205                    if i > 0 {
23206                        self.write(", ");
23207                    }
23208                    self.generate_ordered(ord)?;
23209                }
23210            }
23211        }
23212
23213        if let Some(alias) = &subquery.alias {
23214            self.write_space();
23215            let skip_as = matches!(self.config.dialect, Some(DialectType::Oracle))
23216                || (matches!(self.config.dialect, Some(DialectType::ClickHouse))
23217                    && !subquery.alias_explicit_as);
23218            if !skip_as {
23219                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23220                    self.write(subquery.alias_keyword.as_deref().unwrap_or("AS"));
23221                } else {
23222                    self.write_keyword("AS");
23223                }
23224                self.write_space();
23225            }
23226            self.generate_identifier(alias)?;
23227            if !subquery.column_aliases.is_empty() {
23228                self.write("(");
23229                for (i, col) in subquery.column_aliases.iter().enumerate() {
23230                    if i > 0 {
23231                        self.write(", ");
23232                    }
23233                    self.generate_identifier(col)?;
23234                }
23235                self.write(")");
23236            }
23237        }
23238        // Output trailing comments
23239        for comment in &subquery.trailing_comments {
23240            self.write(" ");
23241            self.write_formatted_comment(comment);
23242        }
23243        Ok(())
23244    }
23245
23246    fn generate_pivot(&mut self, pivot: &Pivot) -> Result<()> {
23247        // Generate WITH clause if present
23248        if let Some(ref with) = pivot.with {
23249            self.generate_with(with)?;
23250            self.write_space();
23251        }
23252
23253        let direction = if pivot.unpivot { "UNPIVOT" } else { "PIVOT" };
23254
23255        // Check for Redshift UNPIVOT in FROM clause:
23256        // UNPIVOT expr [AS val AT attr]
23257        // This is when unpivot=true, expressions is empty, fields is empty, and this is not Null
23258        let is_redshift_unpivot = pivot.unpivot
23259            && pivot.expressions.is_empty()
23260            && pivot.fields.is_empty()
23261            && pivot.using.is_empty()
23262            && pivot.into.is_none()
23263            && !matches!(&pivot.this, Expression::Null(_));
23264
23265        if is_redshift_unpivot {
23266            // Redshift UNPIVOT: UNPIVOT expr [AS alias]
23267            self.write_keyword("UNPIVOT");
23268            self.write_space();
23269            self.generate_expression(&pivot.this)?;
23270            // Alias - for Redshift it can be "val AT attr" format
23271            if let Some(alias) = &pivot.alias {
23272                self.write_space();
23273                self.write_keyword("AS");
23274                self.write_space();
23275                // The alias might contain " AT " for the attr part
23276                self.write(&alias.name);
23277            }
23278            return Ok(());
23279        }
23280
23281        // Check if this is a DuckDB simplified pivot (has `using` or `into`, or no `fields`)
23282        let is_simplified = !pivot.using.is_empty()
23283            || pivot.into.is_some()
23284            || (pivot.fields.is_empty()
23285                && !pivot.expressions.is_empty()
23286                && !matches!(&pivot.this, Expression::Null(_)));
23287
23288        if is_simplified {
23289            // DuckDB simplified syntax:
23290            //   PIVOT table ON cols [IN (...)] USING agg [AS alias], ... [GROUP BY ...]
23291            //   UNPIVOT table ON cols INTO NAME col VALUE col
23292            self.write_keyword(direction);
23293            self.write_space();
23294            self.generate_expression(&pivot.this)?;
23295
23296            if !pivot.expressions.is_empty() {
23297                self.write_space();
23298                self.write_keyword("ON");
23299                self.write_space();
23300                for (i, expr) in pivot.expressions.iter().enumerate() {
23301                    if i > 0 {
23302                        self.write(", ");
23303                    }
23304                    self.generate_expression(expr)?;
23305                }
23306            }
23307
23308            // INTO (for UNPIVOT)
23309            if let Some(into) = &pivot.into {
23310                self.write_space();
23311                self.write_keyword("INTO");
23312                self.write_space();
23313                self.generate_expression(into)?;
23314            }
23315
23316            // USING (for PIVOT)
23317            if !pivot.using.is_empty() {
23318                self.write_space();
23319                self.write_keyword("USING");
23320                self.write_space();
23321                for (i, expr) in pivot.using.iter().enumerate() {
23322                    if i > 0 {
23323                        self.write(", ");
23324                    }
23325                    self.generate_expression(expr)?;
23326                }
23327            }
23328
23329            // GROUP BY
23330            if let Some(group) = &pivot.group {
23331                self.write_space();
23332                self.generate_expression(group)?;
23333            }
23334        } else {
23335            // Standard syntax:
23336            //   table PIVOT(agg [AS alias], ... FOR col IN (val [AS alias], ...) [GROUP BY ...])
23337            //   table UNPIVOT(value_col FOR name_col IN (col1, col2, ...))
23338            // Only output the table expression if it's not a Null (null is used when PIVOT comes after JOIN ON)
23339            if !matches!(&pivot.this, Expression::Null(_)) {
23340                self.generate_expression(&pivot.this)?;
23341                self.write_space();
23342            }
23343            self.write_keyword(direction);
23344            self.write("(");
23345
23346            // Aggregation expressions
23347            for (i, expr) in pivot.expressions.iter().enumerate() {
23348                if i > 0 {
23349                    self.write(", ");
23350                }
23351                self.generate_expression(expr)?;
23352            }
23353
23354            // FOR...IN fields
23355            if !pivot.fields.is_empty() {
23356                if !pivot.expressions.is_empty() {
23357                    self.write_space();
23358                }
23359                self.write_keyword("FOR");
23360                self.write_space();
23361                for (i, field) in pivot.fields.iter().enumerate() {
23362                    if i > 0 {
23363                        self.write_space();
23364                    }
23365                    // field is an In expression: column IN (values)
23366                    self.generate_expression(field)?;
23367                }
23368            }
23369
23370            // DEFAULT ON NULL
23371            if let Some(default_val) = &pivot.default_on_null {
23372                self.write_space();
23373                self.write_keyword("DEFAULT ON NULL");
23374                self.write(" (");
23375                self.generate_expression(default_val)?;
23376                self.write(")");
23377            }
23378
23379            // GROUP BY inside PIVOT parens
23380            if let Some(group) = &pivot.group {
23381                self.write_space();
23382                self.generate_expression(group)?;
23383            }
23384
23385            self.write(")");
23386        }
23387
23388        // Alias
23389        if let Some(alias) = &pivot.alias {
23390            self.write_space();
23391            self.write_keyword("AS");
23392            self.write_space();
23393            self.generate_identifier(alias)?;
23394            self.generate_alias_column_list(&pivot.alias_columns)?;
23395        }
23396
23397        Ok(())
23398    }
23399
23400    fn generate_unpivot(&mut self, unpivot: &Unpivot) -> Result<()> {
23401        self.generate_expression(&unpivot.this)?;
23402        self.write_space();
23403        self.write_keyword("UNPIVOT");
23404        // Output INCLUDE NULLS or EXCLUDE NULLS if specified
23405        if let Some(include) = unpivot.include_nulls {
23406            self.write_space();
23407            if include {
23408                self.write_keyword("INCLUDE NULLS");
23409            } else {
23410                self.write_keyword("EXCLUDE NULLS");
23411            }
23412            self.write_space();
23413        }
23414        self.write("(");
23415        if unpivot.value_column_parenthesized {
23416            self.write("(");
23417        }
23418        self.generate_identifier(&unpivot.value_column)?;
23419        // Output additional value columns if present
23420        for extra_col in &unpivot.extra_value_columns {
23421            self.write(", ");
23422            self.generate_identifier(extra_col)?;
23423        }
23424        if unpivot.value_column_parenthesized {
23425            self.write(")");
23426        }
23427        self.write_space();
23428        self.write_keyword("FOR");
23429        self.write_space();
23430        self.generate_identifier(&unpivot.name_column)?;
23431        self.write_space();
23432        self.write_keyword("IN");
23433        self.write(" (");
23434        for (i, col) in unpivot.columns.iter().enumerate() {
23435            if i > 0 {
23436                self.write(", ");
23437            }
23438            self.generate_expression(col)?;
23439        }
23440        self.write("))");
23441        if let Some(alias) = &unpivot.alias {
23442            self.write_space();
23443            self.write_keyword("AS");
23444            self.write_space();
23445            self.generate_identifier(alias)?;
23446            self.generate_alias_column_list(&unpivot.alias_columns)?;
23447        }
23448        Ok(())
23449    }
23450
23451    fn generate_alias_column_list(&mut self, columns: &[Identifier]) -> Result<()> {
23452        if columns.is_empty() {
23453            return Ok(());
23454        }
23455
23456        self.write("(");
23457        for (i, column) in columns.iter().enumerate() {
23458            if i > 0 {
23459                self.write(", ");
23460            }
23461            self.generate_identifier(column)?;
23462        }
23463        self.write(")");
23464        Ok(())
23465    }
23466
23467    fn generate_values(&mut self, values: &Values) -> Result<()> {
23468        self.write_keyword("VALUES");
23469        for (i, row) in values.expressions.iter().enumerate() {
23470            if i > 0 {
23471                self.write(",");
23472            }
23473            self.write(" (");
23474            for (j, expr) in row.expressions.iter().enumerate() {
23475                if j > 0 {
23476                    self.write(", ");
23477                }
23478                self.generate_expression(expr)?;
23479            }
23480            self.write(")");
23481        }
23482        if let Some(alias) = &values.alias {
23483            self.write_space();
23484            self.write_keyword("AS");
23485            self.write_space();
23486            self.generate_identifier(alias)?;
23487            if !values.column_aliases.is_empty() {
23488                self.write("(");
23489                for (i, col) in values.column_aliases.iter().enumerate() {
23490                    if i > 0 {
23491                        self.write(", ");
23492                    }
23493                    self.generate_identifier(col)?;
23494                }
23495                self.write(")");
23496            }
23497        }
23498        Ok(())
23499    }
23500
23501    fn generate_array(&mut self, arr: &Array) -> Result<()> {
23502        // Apply struct name inheritance for target dialects that need it
23503        let needs_inheritance = matches!(
23504            self.config.dialect,
23505            Some(DialectType::DuckDB)
23506                | Some(DialectType::Spark)
23507                | Some(DialectType::Databricks)
23508                | Some(DialectType::Hive)
23509                | Some(DialectType::Snowflake)
23510                | Some(DialectType::Presto)
23511                | Some(DialectType::Trino)
23512        );
23513        let propagated: Vec<Expression>;
23514        let expressions = if needs_inheritance && arr.expressions.len() > 1 {
23515            propagated = Self::inherit_struct_field_names(&arr.expressions);
23516            &propagated
23517        } else {
23518            &arr.expressions
23519        };
23520
23521        // Generic mode: ARRAY(1, 2, 3) with parentheses
23522        // Dialect mode: ARRAY[1, 2, 3] with brackets (or just [1, 2, 3] if array_bracket_only)
23523        let use_parens =
23524            self.config.dialect.is_none() || self.config.dialect == Some(DialectType::Generic);
23525        if !self.config.array_bracket_only {
23526            self.write_keyword("ARRAY");
23527        }
23528        if use_parens {
23529            self.write("(");
23530        } else {
23531            self.write("[");
23532        }
23533        for (i, expr) in expressions.iter().enumerate() {
23534            if i > 0 {
23535                self.write(", ");
23536            }
23537            self.generate_expression(expr)?;
23538        }
23539        if use_parens {
23540            self.write(")");
23541        } else {
23542            self.write("]");
23543        }
23544        Ok(())
23545    }
23546
23547    fn generate_tuple(&mut self, tuple: &Tuple) -> Result<()> {
23548        // Special case: Tuple(function/expr, TableAlias) pattern for table functions with typed aliases
23549        // Used for PostgreSQL functions like JSON_TO_RECORDSET: FUNC(args) AS alias(col1 type1, col2 type2)
23550        if tuple.expressions.len() == 2 {
23551            if let Expression::TableAlias(_) = &tuple.expressions[1] {
23552                // First element is the function/expression, second is the TableAlias
23553                self.generate_expression(&tuple.expressions[0])?;
23554                self.write_space();
23555                self.write_keyword("AS");
23556                self.write_space();
23557                self.generate_expression(&tuple.expressions[1])?;
23558                return Ok(());
23559            }
23560        }
23561
23562        // In pretty mode, format long tuples with each element on a new line
23563        // Only expand if total width exceeds threshold
23564        let expand_tuple = if self.config.pretty && tuple.expressions.len() > 1 {
23565            let mut expr_strings: Vec<String> = Vec::with_capacity(tuple.expressions.len());
23566            for expr in &tuple.expressions {
23567                expr_strings.push(self.generate_to_string(expr)?);
23568            }
23569            self.too_wide(&expr_strings)
23570        } else {
23571            false
23572        };
23573
23574        if expand_tuple {
23575            self.write("(");
23576            self.write_newline();
23577            self.indent_level += 1;
23578            for (i, expr) in tuple.expressions.iter().enumerate() {
23579                if i > 0 {
23580                    self.write(",");
23581                    self.write_newline();
23582                }
23583                self.write_indent();
23584                self.generate_expression(expr)?;
23585            }
23586            self.indent_level -= 1;
23587            self.write_newline();
23588            self.write_indent();
23589            self.write(")");
23590        } else {
23591            self.write("(");
23592            for (i, expr) in tuple.expressions.iter().enumerate() {
23593                if i > 0 {
23594                    self.write(", ");
23595                }
23596                self.generate_expression(expr)?;
23597            }
23598            self.write(")");
23599        }
23600        Ok(())
23601    }
23602
23603    fn generate_pipe_operator(&mut self, pipe: &PipeOperator) -> Result<()> {
23604        self.generate_expression(&pipe.this)?;
23605        self.write(" |> ");
23606        self.generate_expression(&pipe.expression)?;
23607        Ok(())
23608    }
23609
23610    fn generate_ordered(&mut self, ordered: &Ordered) -> Result<()> {
23611        let unsupported_tsql_null_ordering = ordered.nulls_first.is_some()
23612            && !self.config.null_ordering_supported
23613            && matches!(
23614                self.config.dialect,
23615                Some(DialectType::TSQL) | Some(DialectType::Fabric)
23616            );
23617        let random_ordering = matches!(ordered.this, Expression::Rand(_) | Expression::Random(_));
23618        let emulate_tsql_null_ordering = if let Some(nulls_first) = ordered.nulls_first {
23619            let target_default_nulls_first = !ordered.desc;
23620
23621            unsupported_tsql_null_ordering
23622                && nulls_first != target_default_nulls_first
23623                && !random_ordering
23624        } else {
23625            false
23626        };
23627
23628        if emulate_tsql_null_ordering {
23629            self.write_keyword("CASE WHEN");
23630            self.write_space();
23631            self.generate_expression(&ordered.this)?;
23632            self.write_space();
23633            self.write_keyword("IS NULL THEN 1 ELSE 0 END");
23634            if ordered.nulls_first == Some(true) {
23635                self.write_space();
23636                self.write_keyword("DESC");
23637            }
23638            self.write(", ");
23639        }
23640
23641        self.generate_expression(&ordered.this)?;
23642        if ordered.desc {
23643            self.write_space();
23644            self.write_keyword("DESC");
23645        } else if ordered.explicit_asc {
23646            self.write_space();
23647            self.write_keyword("ASC");
23648        }
23649        if let Some(nulls_first) = ordered.nulls_first {
23650            if !unsupported_tsql_null_ordering
23651                && (self.config.null_ordering_supported
23652                    || !matches!(self.config.dialect, Some(DialectType::Fabric)))
23653            {
23654                // Determine if we should skip outputting NULLS FIRST/LAST when it's the default
23655                // for the dialect. Different dialects have different NULL ordering defaults:
23656                //
23657                // nulls_are_large (Oracle, Postgres, Snowflake, etc.):
23658                //   - ASC: NULLS LAST is default (omit NULLS LAST for ASC)
23659                //   - DESC: NULLS FIRST is default (omit NULLS FIRST for DESC)
23660                //
23661                // nulls_are_small (Spark, Hive, BigQuery, most others):
23662                //   - ASC: NULLS FIRST is default
23663                //   - DESC: NULLS LAST is default
23664                //
23665                // nulls_are_last (DuckDB, Presto, Trino, Dremio, etc.):
23666                //   - NULLS LAST is always the default regardless of sort direction
23667                let is_asc = !ordered.desc;
23668                let is_nulls_are_large = matches!(
23669                    self.config.dialect,
23670                    Some(DialectType::Oracle)
23671                        | Some(DialectType::PostgreSQL)
23672                        | Some(DialectType::Redshift)
23673                        | Some(DialectType::Snowflake)
23674                );
23675                let is_nulls_are_last = matches!(
23676                    self.config.dialect,
23677                    Some(DialectType::Dremio)
23678                        | Some(DialectType::DuckDB)
23679                        | Some(DialectType::Presto)
23680                        | Some(DialectType::Trino)
23681                        | Some(DialectType::Athena)
23682                        | Some(DialectType::ClickHouse)
23683                        | Some(DialectType::Drill)
23684                        | Some(DialectType::Exasol)
23685                );
23686
23687                // Check if the NULLS ordering matches the default for this dialect
23688                let is_default_nulls = if is_nulls_are_large {
23689                    // For nulls_are_large: ASC + NULLS LAST or DESC + NULLS FIRST is default
23690                    (is_asc && !nulls_first) || (!is_asc && nulls_first)
23691                } else if is_nulls_are_last {
23692                    // For nulls_are_last: NULLS LAST is always default
23693                    !nulls_first
23694                } else {
23695                    false
23696                };
23697
23698                if !is_default_nulls {
23699                    self.write_space();
23700                    self.write_keyword("NULLS");
23701                    self.write_space();
23702                    self.write_keyword(if nulls_first { "FIRST" } else { "LAST" });
23703                }
23704            }
23705        }
23706        // WITH FILL clause (ClickHouse)
23707        if let Some(ref with_fill) = ordered.with_fill {
23708            self.write_space();
23709            self.generate_with_fill(with_fill)?;
23710        }
23711        Ok(())
23712    }
23713
23714    /// Write a ClickHouse type string, wrapping in Nullable unless in map key context.
23715    fn write_clickhouse_type(&mut self, type_str: &str) {
23716        if self.clickhouse_nullable_depth < 0 {
23717            // Map key context: don't wrap in Nullable
23718            self.write(type_str);
23719        } else {
23720            self.write(&format!("Nullable({})", type_str));
23721        }
23722    }
23723
23724    fn generate_data_type(&mut self, dt: &DataType) -> Result<()> {
23725        use crate::dialects::DialectType;
23726
23727        match dt {
23728            DataType::Boolean => {
23729                // Dialect-specific boolean type mappings
23730                match self.config.dialect {
23731                    Some(DialectType::TSQL) => self.write_keyword("BIT"),
23732                    Some(DialectType::MySQL) => self.write_keyword("BOOLEAN"), // alias for TINYINT(1)
23733                    Some(DialectType::Oracle) => {
23734                        // Oracle 23c+ supports BOOLEAN, older versions use NUMBER(1)
23735                        self.write_keyword("NUMBER(1)")
23736                    }
23737                    Some(DialectType::ClickHouse) => self.write("Bool"), // ClickHouse uses Bool (case-sensitive)
23738                    _ => self.write_keyword("BOOLEAN"),
23739                }
23740            }
23741            DataType::TinyInt { length } => {
23742                // PostgreSQL, Oracle, and Exasol don't have TINYINT, use SMALLINT
23743                // Dremio maps TINYINT to INT
23744                // ClickHouse maps TINYINT to Int8
23745                match self.config.dialect {
23746                    Some(DialectType::PostgreSQL)
23747                    | Some(DialectType::Redshift)
23748                    | Some(DialectType::Oracle)
23749                    | Some(DialectType::Exasol) => {
23750                        self.write_keyword("SMALLINT");
23751                    }
23752                    Some(DialectType::Teradata) => {
23753                        // Teradata uses BYTEINT for smallest integer
23754                        self.write_keyword("BYTEINT");
23755                    }
23756                    Some(DialectType::Dremio) => {
23757                        // Dremio maps TINYINT to INT
23758                        self.write_keyword("INT");
23759                    }
23760                    Some(DialectType::ClickHouse) => {
23761                        self.write_clickhouse_type("Int8");
23762                    }
23763                    _ => {
23764                        self.write_keyword("TINYINT");
23765                    }
23766                }
23767                if let Some(n) = length {
23768                    if !matches!(
23769                        self.config.dialect,
23770                        Some(DialectType::Dremio) | Some(DialectType::ClickHouse)
23771                    ) {
23772                        self.write(&format!("({})", n));
23773                    }
23774                }
23775            }
23776            DataType::SmallInt { length } => {
23777                // Dremio maps SMALLINT to INT, SQLite/Drill maps SMALLINT to INTEGER
23778                match self.config.dialect {
23779                    Some(DialectType::Dremio) => {
23780                        self.write_keyword("INT");
23781                    }
23782                    Some(DialectType::SQLite) | Some(DialectType::Drill) => {
23783                        self.write_keyword("INTEGER");
23784                    }
23785                    Some(DialectType::BigQuery) => {
23786                        self.write_keyword("INT64");
23787                    }
23788                    Some(DialectType::ClickHouse) => {
23789                        self.write_clickhouse_type("Int16");
23790                    }
23791                    _ => {
23792                        self.write_keyword("SMALLINT");
23793                        if let Some(n) = length {
23794                            self.write(&format!("({})", n));
23795                        }
23796                    }
23797                }
23798            }
23799            DataType::Int {
23800                length,
23801                integer_spelling: _,
23802            } => {
23803                // BigQuery uses INT64 for INT
23804                if matches!(self.config.dialect, Some(DialectType::BigQuery)) {
23805                    self.write_keyword("INT64");
23806                } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23807                    self.write_clickhouse_type("Int32");
23808                } else {
23809                    // TSQL, Presto, Trino, SQLite, Redshift use INTEGER as the canonical form
23810                    let use_integer = match self.config.dialect {
23811                        Some(DialectType::TSQL)
23812                        | Some(DialectType::Fabric)
23813                        | Some(DialectType::Presto)
23814                        | Some(DialectType::Trino)
23815                        | Some(DialectType::SQLite)
23816                        | Some(DialectType::Redshift) => true,
23817                        _ => false,
23818                    };
23819                    if use_integer {
23820                        self.write_keyword("INTEGER");
23821                    } else {
23822                        self.write_keyword("INT");
23823                    }
23824                    if let Some(n) = length {
23825                        self.write(&format!("({})", n));
23826                    }
23827                }
23828            }
23829            DataType::BigInt { length } => {
23830                // Dialect-specific bigint type mappings
23831                match self.config.dialect {
23832                    Some(DialectType::Oracle) => {
23833                        // Oracle doesn't have BIGINT, uses INT
23834                        self.write_keyword("INT");
23835                    }
23836                    Some(DialectType::ClickHouse) => {
23837                        self.write_clickhouse_type("Int64");
23838                    }
23839                    _ => {
23840                        self.write_keyword("BIGINT");
23841                        if let Some(n) = length {
23842                            self.write(&format!("({})", n));
23843                        }
23844                    }
23845                }
23846            }
23847            DataType::Float {
23848                precision,
23849                scale,
23850                real_spelling,
23851            } => {
23852                // Dialect-specific float type mappings
23853                // If real_spelling is true, preserve REAL; otherwise use dialect default
23854                // Spark/Hive don't support REAL, always use FLOAT
23855                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
23856                    self.write_clickhouse_type("Float32");
23857                } else if *real_spelling
23858                    && !matches!(
23859                        self.config.dialect,
23860                        Some(DialectType::Spark)
23861                            | Some(DialectType::Databricks)
23862                            | Some(DialectType::Hive)
23863                            | Some(DialectType::Snowflake)
23864                            | Some(DialectType::MySQL)
23865                            | Some(DialectType::BigQuery)
23866                    )
23867                {
23868                    self.write_keyword("REAL")
23869                } else {
23870                    match self.config.dialect {
23871                        Some(DialectType::PostgreSQL) => self.write_keyword("REAL"),
23872                        Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
23873                        _ => self.write_keyword("FLOAT"),
23874                    }
23875                }
23876                // MySQL supports FLOAT(precision) or FLOAT(precision, scale)
23877                // Spark/Hive don't support FLOAT(precision)
23878                if !matches!(
23879                    self.config.dialect,
23880                    Some(DialectType::Spark)
23881                        | Some(DialectType::Databricks)
23882                        | Some(DialectType::Hive)
23883                        | Some(DialectType::Presto)
23884                        | Some(DialectType::Trino)
23885                ) {
23886                    if let Some(p) = precision {
23887                        self.write(&format!("({}", p));
23888                        if let Some(s) = scale {
23889                            self.write(&format!(", {})", s));
23890                        } else {
23891                            self.write(")");
23892                        }
23893                    }
23894                }
23895            }
23896            DataType::Double { precision, scale } => {
23897                // Dialect-specific double type mappings
23898                match self.config.dialect {
23899                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
23900                        self.write_keyword("FLOAT")
23901                    } // SQL Server/Fabric FLOAT is double
23902                    Some(DialectType::Oracle) => self.write_keyword("DOUBLE PRECISION"),
23903                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("Float64"),
23904                    Some(DialectType::BigQuery) => self.write_keyword("FLOAT64"),
23905                    Some(DialectType::SQLite) => self.write_keyword("REAL"),
23906                    Some(DialectType::PostgreSQL)
23907                    | Some(DialectType::Redshift)
23908                    | Some(DialectType::Teradata)
23909                    | Some(DialectType::Materialize) => self.write_keyword("DOUBLE PRECISION"),
23910                    _ => self.write_keyword("DOUBLE"),
23911                }
23912                // MySQL supports DOUBLE(precision, scale)
23913                if let Some(p) = precision {
23914                    self.write(&format!("({}", p));
23915                    if let Some(s) = scale {
23916                        self.write(&format!(", {})", s));
23917                    } else {
23918                        self.write(")");
23919                    }
23920                }
23921            }
23922            DataType::Decimal { precision, scale } => {
23923                // Dialect-specific decimal type mappings
23924                match self.config.dialect {
23925                    Some(DialectType::ClickHouse) => {
23926                        self.write("Decimal");
23927                        if let Some(p) = precision {
23928                            self.write(&format!("({}", p));
23929                            if let Some(s) = scale {
23930                                self.write(&format!(", {}", s));
23931                            }
23932                            self.write(")");
23933                        }
23934                    }
23935                    Some(DialectType::Oracle) => {
23936                        // Oracle uses NUMBER instead of DECIMAL
23937                        self.write_keyword("NUMBER");
23938                        if let Some(p) = precision {
23939                            self.write(&format!("({}", p));
23940                            if let Some(s) = scale {
23941                                self.write(&format!(", {}", s));
23942                            }
23943                            self.write(")");
23944                        }
23945                    }
23946                    Some(DialectType::BigQuery) => {
23947                        // BigQuery uses NUMERIC instead of DECIMAL
23948                        self.write_keyword("NUMERIC");
23949                        if let Some(p) = precision {
23950                            self.write(&format!("({}", p));
23951                            if let Some(s) = scale {
23952                                self.write(&format!(", {}", s));
23953                            }
23954                            self.write(")");
23955                        }
23956                    }
23957                    _ => {
23958                        self.write_keyword("DECIMAL");
23959                        if let Some(p) = precision {
23960                            self.write(&format!("({}", p));
23961                            if let Some(s) = scale {
23962                                self.write(&format!(", {}", s));
23963                            }
23964                            self.write(")");
23965                        }
23966                    }
23967                }
23968            }
23969            DataType::Char { length } => {
23970                // Dialect-specific char type mappings
23971                match self.config.dialect {
23972                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
23973                        // DuckDB/SQLite maps CHAR to TEXT
23974                        self.write_keyword("TEXT");
23975                    }
23976                    Some(DialectType::Hive)
23977                    | Some(DialectType::Spark)
23978                    | Some(DialectType::Databricks) => {
23979                        // Hive/Spark/Databricks maps CHAR to STRING (when no length)
23980                        // CHAR(n) with explicit length is kept as CHAR(n) for Spark/Databricks
23981                        if length.is_some()
23982                            && !matches!(self.config.dialect, Some(DialectType::Hive))
23983                        {
23984                            self.write_keyword("CHAR");
23985                            if let Some(n) = length {
23986                                self.write(&format!("({})", n));
23987                            }
23988                        } else {
23989                            self.write_keyword("STRING");
23990                        }
23991                    }
23992                    Some(DialectType::Dremio) => {
23993                        // Dremio maps CHAR to VARCHAR
23994                        self.write_keyword("VARCHAR");
23995                        if let Some(n) = length {
23996                            self.write(&format!("({})", n));
23997                        }
23998                    }
23999                    _ => {
24000                        self.write_keyword("CHAR");
24001                        if let Some(n) = length {
24002                            self.write(&format!("({})", n));
24003                        }
24004                    }
24005                }
24006            }
24007            DataType::VarChar {
24008                length,
24009                parenthesized_length,
24010            } => {
24011                // Dialect-specific varchar type mappings
24012                match self.config.dialect {
24013                    Some(DialectType::Oracle) => {
24014                        self.write_keyword("VARCHAR2");
24015                        if let Some(n) = length {
24016                            self.write(&format!("({})", n));
24017                        }
24018                    }
24019                    Some(DialectType::DuckDB) => {
24020                        // DuckDB maps VARCHAR to TEXT, preserving length
24021                        self.write_keyword("TEXT");
24022                        if let Some(n) = length {
24023                            self.write(&format!("({})", n));
24024                        }
24025                    }
24026                    Some(DialectType::SQLite) => {
24027                        // SQLite maps VARCHAR to TEXT, preserving length
24028                        self.write_keyword("TEXT");
24029                        if let Some(n) = length {
24030                            self.write(&format!("({})", n));
24031                        }
24032                    }
24033                    Some(DialectType::MySQL) if length.is_none() => {
24034                        // MySQL requires VARCHAR to have a size - if it doesn't, use TEXT
24035                        self.write_keyword("TEXT");
24036                    }
24037                    Some(DialectType::Hive)
24038                    | Some(DialectType::Spark)
24039                    | Some(DialectType::Databricks)
24040                        if length.is_none() =>
24041                    {
24042                        // Hive/Spark/Databricks: VARCHAR without length → STRING
24043                        self.write_keyword("STRING");
24044                    }
24045                    _ => {
24046                        self.write_keyword("VARCHAR");
24047                        if let Some(n) = length {
24048                            // Hive uses VARCHAR((n)) with extra parentheses in STRUCT definitions
24049                            if *parenthesized_length {
24050                                self.write(&format!("(({}))", n));
24051                            } else {
24052                                self.write(&format!("({})", n));
24053                            }
24054                        }
24055                    }
24056                }
24057            }
24058            DataType::Text => {
24059                // Dialect-specific text type mappings
24060                match self.config.dialect {
24061                    Some(DialectType::Oracle) => self.write_keyword("CLOB"),
24062                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24063                        self.write_keyword("VARCHAR(MAX)")
24064                    }
24065                    Some(DialectType::BigQuery) => self.write_keyword("STRING"),
24066                    Some(DialectType::Snowflake)
24067                    | Some(DialectType::Dremio)
24068                    | Some(DialectType::Drill) => self.write_keyword("VARCHAR"),
24069                    Some(DialectType::Exasol) => self.write_keyword("LONG VARCHAR"),
24070                    Some(DialectType::Presto)
24071                    | Some(DialectType::Trino)
24072                    | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
24073                    Some(DialectType::Spark)
24074                    | Some(DialectType::Databricks)
24075                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
24076                    Some(DialectType::Redshift) => self.write_keyword("VARCHAR(MAX)"),
24077                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
24078                        self.write_keyword("STRING")
24079                    }
24080                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
24081                    _ => self.write_keyword("TEXT"),
24082                }
24083            }
24084            DataType::TextWithLength { length } => {
24085                // TEXT(n) - dialect-specific type with length
24086                match self.config.dialect {
24087                    Some(DialectType::Oracle) => self.write(&format!("CLOB({})", length)),
24088                    Some(DialectType::Hive)
24089                    | Some(DialectType::Spark)
24090                    | Some(DialectType::Databricks) => {
24091                        self.write(&format!("VARCHAR({})", length));
24092                    }
24093                    Some(DialectType::Redshift) => self.write(&format!("VARCHAR({})", length)),
24094                    Some(DialectType::BigQuery) => self.write(&format!("STRING({})", length)),
24095                    Some(DialectType::Snowflake)
24096                    | Some(DialectType::Presto)
24097                    | Some(DialectType::Trino)
24098                    | Some(DialectType::Athena)
24099                    | Some(DialectType::Drill)
24100                    | Some(DialectType::Dremio) => {
24101                        self.write(&format!("VARCHAR({})", length));
24102                    }
24103                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24104                        self.write(&format!("VARCHAR({})", length))
24105                    }
24106                    Some(DialectType::StarRocks) | Some(DialectType::Doris) => {
24107                        self.write(&format!("STRING({})", length))
24108                    }
24109                    Some(DialectType::ClickHouse) => self.write_clickhouse_type("String"),
24110                    _ => self.write(&format!("TEXT({})", length)),
24111                }
24112            }
24113            DataType::String { length } => {
24114                // STRING type with optional length (BigQuery STRING(n))
24115                match self.config.dialect {
24116                    Some(DialectType::ClickHouse) => {
24117                        // ClickHouse uses String with specific casing
24118                        self.write("String");
24119                        if let Some(n) = length {
24120                            self.write(&format!("({})", n));
24121                        }
24122                    }
24123                    Some(DialectType::BigQuery)
24124                    | Some(DialectType::Hive)
24125                    | Some(DialectType::Spark)
24126                    | Some(DialectType::Databricks)
24127                    | Some(DialectType::StarRocks)
24128                    | Some(DialectType::Doris) => {
24129                        self.write_keyword("STRING");
24130                        if let Some(n) = length {
24131                            self.write(&format!("({})", n));
24132                        }
24133                    }
24134                    Some(DialectType::PostgreSQL) => {
24135                        // PostgreSQL doesn't have STRING - use VARCHAR or TEXT
24136                        if let Some(n) = length {
24137                            self.write_keyword("VARCHAR");
24138                            self.write(&format!("({})", n));
24139                        } else {
24140                            self.write_keyword("TEXT");
24141                        }
24142                    }
24143                    Some(DialectType::Redshift) => {
24144                        // Redshift: STRING -> VARCHAR(MAX)
24145                        if let Some(n) = length {
24146                            self.write_keyword("VARCHAR");
24147                            self.write(&format!("({})", n));
24148                        } else {
24149                            self.write_keyword("VARCHAR(MAX)");
24150                        }
24151                    }
24152                    Some(DialectType::MySQL) => {
24153                        // MySQL doesn't have STRING - use VARCHAR or TEXT
24154                        if let Some(n) = length {
24155                            self.write_keyword("VARCHAR");
24156                            self.write(&format!("({})", n));
24157                        } else {
24158                            self.write_keyword("TEXT");
24159                        }
24160                    }
24161                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24162                        // TSQL: STRING -> VARCHAR(MAX)
24163                        if let Some(n) = length {
24164                            self.write_keyword("VARCHAR");
24165                            self.write(&format!("({})", n));
24166                        } else {
24167                            self.write_keyword("VARCHAR(MAX)");
24168                        }
24169                    }
24170                    Some(DialectType::Oracle) => {
24171                        // Oracle: STRING -> CLOB
24172                        self.write_keyword("CLOB");
24173                    }
24174                    Some(DialectType::DuckDB) | Some(DialectType::Materialize) => {
24175                        // DuckDB/Materialize uses TEXT for string types
24176                        self.write_keyword("TEXT");
24177                        if let Some(n) = length {
24178                            self.write(&format!("({})", n));
24179                        }
24180                    }
24181                    Some(DialectType::Presto)
24182                    | Some(DialectType::Trino)
24183                    | Some(DialectType::Drill)
24184                    | Some(DialectType::Dremio) => {
24185                        // Presto/Trino/Drill use VARCHAR for string types
24186                        self.write_keyword("VARCHAR");
24187                        if let Some(n) = length {
24188                            self.write(&format!("({})", n));
24189                        }
24190                    }
24191                    Some(DialectType::Snowflake) => {
24192                        // Snowflake: STRING stays as STRING (identity/DDL)
24193                        // CAST context STRING -> VARCHAR is handled in generate_cast
24194                        self.write_keyword("STRING");
24195                        if let Some(n) = length {
24196                            self.write(&format!("({})", n));
24197                        }
24198                    }
24199                    _ => {
24200                        // Default: output STRING with optional length
24201                        self.write_keyword("STRING");
24202                        if let Some(n) = length {
24203                            self.write(&format!("({})", n));
24204                        }
24205                    }
24206                }
24207            }
24208            DataType::Binary { length } => {
24209                // Dialect-specific binary type mappings
24210                match self.config.dialect {
24211                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
24212                        self.write_keyword("BYTEA");
24213                        if let Some(n) = length {
24214                            self.write(&format!("({})", n));
24215                        }
24216                    }
24217                    Some(DialectType::Redshift) => {
24218                        self.write_keyword("VARBYTE");
24219                        if let Some(n) = length {
24220                            self.write(&format!("({})", n));
24221                        }
24222                    }
24223                    Some(DialectType::DuckDB)
24224                    | Some(DialectType::SQLite)
24225                    | Some(DialectType::Oracle) => {
24226                        // DuckDB/SQLite/Oracle maps BINARY to BLOB
24227                        self.write_keyword("BLOB");
24228                        if let Some(n) = length {
24229                            self.write(&format!("({})", n));
24230                        }
24231                    }
24232                    Some(DialectType::Presto)
24233                    | Some(DialectType::Trino)
24234                    | Some(DialectType::Athena)
24235                    | Some(DialectType::Drill)
24236                    | Some(DialectType::Dremio) => {
24237                        // These dialects map BINARY to VARBINARY
24238                        self.write_keyword("VARBINARY");
24239                        if let Some(n) = length {
24240                            self.write(&format!("({})", n));
24241                        }
24242                    }
24243                    Some(DialectType::ClickHouse) => {
24244                        // ClickHouse: wrap BINARY in Nullable (unless map key context)
24245                        if self.clickhouse_nullable_depth < 0 {
24246                            self.write("BINARY");
24247                        } else {
24248                            self.write("Nullable(BINARY");
24249                        }
24250                        if let Some(n) = length {
24251                            self.write(&format!("({})", n));
24252                        }
24253                        if self.clickhouse_nullable_depth >= 0 {
24254                            self.write(")");
24255                        }
24256                    }
24257                    _ => {
24258                        self.write_keyword("BINARY");
24259                        if let Some(n) = length {
24260                            self.write(&format!("({})", n));
24261                        }
24262                    }
24263                }
24264            }
24265            DataType::VarBinary { length } => {
24266                // Dialect-specific varbinary type mappings
24267                match self.config.dialect {
24268                    Some(DialectType::PostgreSQL) | Some(DialectType::Materialize) => {
24269                        self.write_keyword("BYTEA");
24270                        if let Some(n) = length {
24271                            self.write(&format!("({})", n));
24272                        }
24273                    }
24274                    Some(DialectType::Redshift) => {
24275                        self.write_keyword("VARBYTE");
24276                        if let Some(n) = length {
24277                            self.write(&format!("({})", n));
24278                        }
24279                    }
24280                    Some(DialectType::DuckDB)
24281                    | Some(DialectType::SQLite)
24282                    | Some(DialectType::Oracle) => {
24283                        // DuckDB/SQLite/Oracle maps VARBINARY to BLOB
24284                        self.write_keyword("BLOB");
24285                        if let Some(n) = length {
24286                            self.write(&format!("({})", n));
24287                        }
24288                    }
24289                    Some(DialectType::Exasol) => {
24290                        // Exasol maps VARBINARY to VARCHAR
24291                        self.write_keyword("VARCHAR");
24292                    }
24293                    Some(DialectType::Spark)
24294                    | Some(DialectType::Hive)
24295                    | Some(DialectType::Databricks) => {
24296                        // Spark/Hive use BINARY instead of VARBINARY
24297                        self.write_keyword("BINARY");
24298                        if let Some(n) = length {
24299                            self.write(&format!("({})", n));
24300                        }
24301                    }
24302                    Some(DialectType::ClickHouse) => {
24303                        // ClickHouse maps VARBINARY to String (wrapped in Nullable unless map key)
24304                        self.write_clickhouse_type("String");
24305                    }
24306                    _ => {
24307                        self.write_keyword("VARBINARY");
24308                        if let Some(n) = length {
24309                            self.write(&format!("({})", n));
24310                        }
24311                    }
24312                }
24313            }
24314            DataType::Blob => {
24315                // Dialect-specific blob type mappings
24316                match self.config.dialect {
24317                    Some(DialectType::PostgreSQL) => self.write_keyword("BYTEA"),
24318                    Some(DialectType::Redshift) => self.write_keyword("VARBYTE"),
24319                    Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
24320                        self.write_keyword("VARBINARY")
24321                    }
24322                    Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
24323                    Some(DialectType::Exasol) => self.write_keyword("VARCHAR"),
24324                    Some(DialectType::Presto)
24325                    | Some(DialectType::Trino)
24326                    | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
24327                    Some(DialectType::DuckDB) => {
24328                        // Python sqlglot: BLOB -> VARBINARY for DuckDB (base TYPE_MAPPING)
24329                        // DuckDB identity works via: BLOB -> transform VarBinary -> generator BLOB
24330                        self.write_keyword("VARBINARY");
24331                    }
24332                    Some(DialectType::Spark)
24333                    | Some(DialectType::Databricks)
24334                    | Some(DialectType::Hive) => self.write_keyword("BINARY"),
24335                    Some(DialectType::ClickHouse) => {
24336                        // BLOB maps to Nullable(String) in ClickHouse, even in column defs
24337                        // where we normally suppress Nullable wrapping (clickhouse_nullable_depth = -1).
24338                        // This matches Python sqlglot behavior.
24339                        self.write("Nullable(String)");
24340                    }
24341                    _ => self.write_keyword("BLOB"),
24342                }
24343            }
24344            DataType::Bit { length } => {
24345                // Dialect-specific bit type mappings
24346                match self.config.dialect {
24347                    Some(DialectType::Dremio)
24348                    | Some(DialectType::Spark)
24349                    | Some(DialectType::Databricks)
24350                    | Some(DialectType::Hive)
24351                    | Some(DialectType::Snowflake)
24352                    | Some(DialectType::BigQuery)
24353                    | Some(DialectType::Presto)
24354                    | Some(DialectType::Trino)
24355                    | Some(DialectType::ClickHouse)
24356                    | Some(DialectType::Redshift) => {
24357                        // These dialects don't support BIT type, use BOOLEAN
24358                        self.write_keyword("BOOLEAN");
24359                    }
24360                    _ => {
24361                        self.write_keyword("BIT");
24362                        if let Some(n) = length {
24363                            self.write(&format!("({})", n));
24364                        }
24365                    }
24366                }
24367            }
24368            DataType::VarBit { length } => {
24369                self.write_keyword("VARBIT");
24370                if let Some(n) = length {
24371                    self.write(&format!("({})", n));
24372                }
24373            }
24374            DataType::Date => self.write_keyword("DATE"),
24375            DataType::Time {
24376                precision,
24377                timezone,
24378            } => {
24379                if *timezone {
24380                    // Dialect-specific TIME WITH TIME ZONE output
24381                    match self.config.dialect {
24382                        Some(DialectType::DuckDB) => {
24383                            // DuckDB: TIMETZ (drops precision)
24384                            self.write_keyword("TIMETZ");
24385                        }
24386                        Some(DialectType::PostgreSQL) => {
24387                            // PostgreSQL: TIMETZ or TIMETZ(p)
24388                            self.write_keyword("TIMETZ");
24389                            if let Some(p) = precision {
24390                                self.write(&format!("({})", p));
24391                            }
24392                        }
24393                        _ => {
24394                            // Presto/Trino/Redshift/others: TIME(p) WITH TIME ZONE
24395                            self.write_keyword("TIME");
24396                            if let Some(p) = precision {
24397                                self.write(&format!("({})", p));
24398                            }
24399                            self.write_keyword(" WITH TIME ZONE");
24400                        }
24401                    }
24402                } else {
24403                    // Spark/Hive/Databricks: TIME -> TIMESTAMP (TIME not supported)
24404                    if matches!(
24405                        self.config.dialect,
24406                        Some(DialectType::Spark)
24407                            | Some(DialectType::Databricks)
24408                            | Some(DialectType::Hive)
24409                    ) {
24410                        self.write_keyword("TIMESTAMP");
24411                    } else {
24412                        self.write_keyword("TIME");
24413                        if let Some(p) = precision {
24414                            self.write(&format!("({})", p));
24415                        }
24416                    }
24417                }
24418            }
24419            DataType::Timestamp {
24420                precision,
24421                timezone,
24422            } => {
24423                // Dialect-specific timestamp type mappings
24424                match self.config.dialect {
24425                    Some(DialectType::Snowflake) if *timezone => {
24426                        self.write_keyword("TIMESTAMPTZ");
24427                        if let Some(p) = precision {
24428                            self.write(&format!("({})", p));
24429                        }
24430                    }
24431                    Some(DialectType::ClickHouse) => {
24432                        self.write("DateTime");
24433                        if let Some(p) = precision {
24434                            self.write(&format!("({})", p));
24435                        }
24436                    }
24437                    Some(DialectType::TSQL) => {
24438                        if *timezone {
24439                            self.write_keyword("DATETIMEOFFSET");
24440                        } else {
24441                            self.write_keyword("DATETIME2");
24442                        }
24443                        if let Some(p) = precision {
24444                            self.write(&format!("({})", p));
24445                        }
24446                    }
24447                    Some(DialectType::MySQL) => {
24448                        // MySQL: TIMESTAMP stays as TIMESTAMP in DDL; CAST mapping handled separately
24449                        self.write_keyword("TIMESTAMP");
24450                        if let Some(p) = precision {
24451                            self.write(&format!("({})", p));
24452                        }
24453                    }
24454                    Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
24455                        // Doris/StarRocks: TIMESTAMP -> DATETIME
24456                        self.write_keyword("DATETIME");
24457                        if let Some(p) = precision {
24458                            self.write(&format!("({})", p));
24459                        }
24460                    }
24461                    Some(DialectType::BigQuery) => {
24462                        // BigQuery: TIMESTAMP is always UTC, DATETIME is timezone-naive
24463                        if *timezone {
24464                            self.write_keyword("TIMESTAMP");
24465                        } else {
24466                            self.write_keyword("DATETIME");
24467                        }
24468                    }
24469                    Some(DialectType::DuckDB) => {
24470                        // DuckDB: TIMESTAMPTZ shorthand
24471                        if *timezone {
24472                            self.write_keyword("TIMESTAMPTZ");
24473                        } else {
24474                            self.write_keyword("TIMESTAMP");
24475                            if let Some(p) = precision {
24476                                self.write(&format!("({})", p));
24477                            }
24478                        }
24479                    }
24480                    _ => {
24481                        if *timezone && !self.config.tz_to_with_time_zone {
24482                            // Use TIMESTAMPTZ shorthand when dialect doesn't prefer WITH TIME ZONE
24483                            self.write_keyword("TIMESTAMPTZ");
24484                            if let Some(p) = precision {
24485                                self.write(&format!("({})", p));
24486                            }
24487                        } else {
24488                            self.write_keyword("TIMESTAMP");
24489                            if let Some(p) = precision {
24490                                self.write(&format!("({})", p));
24491                            }
24492                            if *timezone {
24493                                self.write_space();
24494                                self.write_keyword("WITH TIME ZONE");
24495                            }
24496                        }
24497                    }
24498                }
24499            }
24500            DataType::Interval { unit, to } => {
24501                self.write_keyword("INTERVAL");
24502                if let Some(u) = unit {
24503                    self.write_space();
24504                    self.write_keyword(u);
24505                }
24506                // Handle range intervals like DAY TO HOUR
24507                if let Some(t) = to {
24508                    self.write_space();
24509                    self.write_keyword("TO");
24510                    self.write_space();
24511                    self.write_keyword(t);
24512                }
24513            }
24514            DataType::Json => {
24515                // Dialect-specific JSON type mappings
24516                match self.config.dialect {
24517                    Some(DialectType::Oracle) => self.write_keyword("JSON"), // Oracle 21c+
24518                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"), // No native JSON type
24519                    Some(DialectType::MySQL) => self.write_keyword("JSON"),
24520                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24521                    _ => self.write_keyword("JSON"),
24522                }
24523            }
24524            DataType::JsonB => {
24525                // JSONB is PostgreSQL specific, but Doris also supports it
24526                match self.config.dialect {
24527                    Some(DialectType::PostgreSQL) => self.write_keyword("JSONB"),
24528                    Some(DialectType::Doris) => self.write_keyword("JSONB"),
24529                    Some(DialectType::Snowflake) => self.write_keyword("VARIANT"),
24530                    Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24531                    Some(DialectType::DuckDB) => self.write_keyword("JSON"), // DuckDB maps JSONB to JSON
24532                    _ => self.write_keyword("JSON"), // Fall back to JSON for other dialects
24533                }
24534            }
24535            DataType::Uuid => {
24536                // Dialect-specific UUID type mappings
24537                match self.config.dialect {
24538                    Some(DialectType::TSQL) => self.write_keyword("UNIQUEIDENTIFIER"),
24539                    Some(DialectType::MySQL) => self.write_keyword("CHAR(36)"),
24540                    Some(DialectType::Oracle) => self.write_keyword("RAW(16)"),
24541                    Some(DialectType::BigQuery)
24542                    | Some(DialectType::Spark)
24543                    | Some(DialectType::Databricks) => self.write_keyword("STRING"),
24544                    _ => self.write_keyword("UUID"),
24545                }
24546            }
24547            DataType::Array {
24548                element_type,
24549                dimension,
24550            } => {
24551                // Dialect-specific array syntax
24552                match self.config.dialect {
24553                    Some(DialectType::PostgreSQL)
24554                    | Some(DialectType::Redshift)
24555                    | Some(DialectType::DuckDB) => {
24556                        // PostgreSQL uses TYPE[] or TYPE[N] syntax
24557                        self.generate_data_type(element_type)?;
24558                        if let Some(dim) = dimension {
24559                            self.write(&format!("[{}]", dim));
24560                        } else {
24561                            self.write("[]");
24562                        }
24563                    }
24564                    Some(DialectType::BigQuery) => {
24565                        self.write_keyword("ARRAY<");
24566                        self.generate_data_type(element_type)?;
24567                        self.write(">");
24568                    }
24569                    Some(DialectType::Snowflake)
24570                    | Some(DialectType::Presto)
24571                    | Some(DialectType::Trino)
24572                    | Some(DialectType::ClickHouse) => {
24573                        // These dialects use Array(TYPE) parentheses syntax
24574                        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24575                            self.write("Array(");
24576                        } else {
24577                            self.write_keyword("ARRAY(");
24578                        }
24579                        self.generate_data_type(element_type)?;
24580                        self.write(")");
24581                    }
24582                    Some(DialectType::TSQL)
24583                    | Some(DialectType::MySQL)
24584                    | Some(DialectType::Oracle) => {
24585                        // These dialects don't have native array types
24586                        // Fall back to JSON or use native workarounds
24587                        match self.config.dialect {
24588                            Some(DialectType::MySQL) => self.write_keyword("JSON"),
24589                            Some(DialectType::TSQL) => self.write_keyword("NVARCHAR(MAX)"),
24590                            _ => self.write_keyword("JSON"),
24591                        }
24592                    }
24593                    _ => {
24594                        // Default: use angle bracket syntax (ARRAY<T>)
24595                        self.write_keyword("ARRAY<");
24596                        self.generate_data_type(element_type)?;
24597                        self.write(">");
24598                    }
24599                }
24600            }
24601            DataType::List { element_type } => {
24602                // Materialize: element_type LIST (postfix syntax)
24603                self.generate_data_type(element_type)?;
24604                self.write_keyword(" LIST");
24605            }
24606            DataType::Map {
24607                key_type,
24608                value_type,
24609            } => {
24610                // Use parentheses for Snowflake and RisingWave, bracket syntax for Materialize, angle brackets for others
24611                match self.config.dialect {
24612                    Some(DialectType::Materialize) => {
24613                        // Materialize: MAP[key_type => value_type]
24614                        self.write_keyword("MAP[");
24615                        self.generate_data_type(key_type)?;
24616                        self.write(" => ");
24617                        self.generate_data_type(value_type)?;
24618                        self.write("]");
24619                    }
24620                    Some(DialectType::Snowflake)
24621                    | Some(DialectType::RisingWave)
24622                    | Some(DialectType::DuckDB)
24623                    | Some(DialectType::Presto)
24624                    | Some(DialectType::Trino)
24625                    | Some(DialectType::Athena) => {
24626                        self.write_keyword("MAP(");
24627                        self.generate_data_type(key_type)?;
24628                        self.write(", ");
24629                        self.generate_data_type(value_type)?;
24630                        self.write(")");
24631                    }
24632                    Some(DialectType::ClickHouse) => {
24633                        // ClickHouse: Map(key_type, value_type) with parenthesized syntax
24634                        // Key types must NOT be wrapped in Nullable
24635                        self.write("Map(");
24636                        self.clickhouse_nullable_depth = -1; // suppress Nullable for key
24637                        self.generate_data_type(key_type)?;
24638                        self.clickhouse_nullable_depth = 0;
24639                        self.write(", ");
24640                        self.generate_data_type(value_type)?;
24641                        self.write(")");
24642                    }
24643                    _ => {
24644                        self.write_keyword("MAP<");
24645                        self.generate_data_type(key_type)?;
24646                        self.write(", ");
24647                        self.generate_data_type(value_type)?;
24648                        self.write(">");
24649                    }
24650                }
24651            }
24652            DataType::Vector {
24653                element_type,
24654                dimension,
24655            } => {
24656                if matches!(self.config.dialect, Some(DialectType::SingleStore)) {
24657                    // SingleStore format: VECTOR(dimension, type_alias)
24658                    self.write_keyword("VECTOR(");
24659                    if let Some(dim) = dimension {
24660                        self.write(&dim.to_string());
24661                    }
24662                    // Map type back to SingleStore alias
24663                    let type_alias = element_type.as_ref().and_then(|et| match et.as_ref() {
24664                        DataType::TinyInt { .. } => Some("I8"),
24665                        DataType::SmallInt { .. } => Some("I16"),
24666                        DataType::Int { .. } => Some("I32"),
24667                        DataType::BigInt { .. } => Some("I64"),
24668                        DataType::Float { .. } => Some("F32"),
24669                        DataType::Double { .. } => Some("F64"),
24670                        _ => None,
24671                    });
24672                    if let Some(alias) = type_alias {
24673                        if dimension.is_some() {
24674                            self.write(", ");
24675                        }
24676                        self.write(alias);
24677                    }
24678                    self.write(")");
24679                } else {
24680                    // Snowflake format: VECTOR(type, dimension)
24681                    self.write_keyword("VECTOR(");
24682                    if let Some(ref et) = element_type {
24683                        self.generate_data_type(et)?;
24684                        if dimension.is_some() {
24685                            self.write(", ");
24686                        }
24687                    }
24688                    if let Some(dim) = dimension {
24689                        self.write(&dim.to_string());
24690                    }
24691                    self.write(")");
24692                }
24693            }
24694            DataType::Object { fields, modifier } => {
24695                self.write_keyword("OBJECT(");
24696                for (i, (name, dt, not_null)) in fields.iter().enumerate() {
24697                    if i > 0 {
24698                        self.write(", ");
24699                    }
24700                    self.write(name);
24701                    self.write(" ");
24702                    self.generate_data_type(dt)?;
24703                    if *not_null {
24704                        self.write_keyword(" NOT NULL");
24705                    }
24706                }
24707                self.write(")");
24708                if let Some(mod_str) = modifier {
24709                    self.write(" ");
24710                    self.write_keyword(mod_str);
24711                }
24712            }
24713            DataType::Struct { fields, nested } => {
24714                // Dialect-specific struct type mappings
24715                match self.config.dialect {
24716                    Some(DialectType::Snowflake) => {
24717                        // Snowflake maps STRUCT to OBJECT
24718                        self.write_keyword("OBJECT(");
24719                        for (i, field) in fields.iter().enumerate() {
24720                            if i > 0 {
24721                                self.write(", ");
24722                            }
24723                            if !field.name.is_empty() {
24724                                self.write(&field.name);
24725                                self.write(" ");
24726                            }
24727                            self.generate_data_type(&field.data_type)?;
24728                        }
24729                        self.write(")");
24730                    }
24731                    Some(DialectType::Presto) | Some(DialectType::Trino) => {
24732                        // Presto/Trino use ROW(name TYPE, ...) syntax
24733                        self.write_keyword("ROW(");
24734                        for (i, field) in fields.iter().enumerate() {
24735                            if i > 0 {
24736                                self.write(", ");
24737                            }
24738                            if !field.name.is_empty() {
24739                                self.write(&field.name);
24740                                self.write(" ");
24741                            }
24742                            self.generate_data_type(&field.data_type)?;
24743                        }
24744                        self.write(")");
24745                    }
24746                    Some(DialectType::DuckDB) => {
24747                        // DuckDB uses parenthesized syntax: STRUCT(name TYPE, ...)
24748                        self.write_keyword("STRUCT(");
24749                        for (i, field) in fields.iter().enumerate() {
24750                            if i > 0 {
24751                                self.write(", ");
24752                            }
24753                            if !field.name.is_empty() {
24754                                self.write(&field.name);
24755                                self.write(" ");
24756                            }
24757                            self.generate_data_type(&field.data_type)?;
24758                        }
24759                        self.write(")");
24760                    }
24761                    Some(DialectType::ClickHouse) => {
24762                        // ClickHouse uses Tuple(name TYPE, ...) for struct types
24763                        self.write("Tuple(");
24764                        for (i, field) in fields.iter().enumerate() {
24765                            if i > 0 {
24766                                self.write(", ");
24767                            }
24768                            if !field.name.is_empty() {
24769                                self.write(&field.name);
24770                                self.write(" ");
24771                            }
24772                            self.generate_data_type(&field.data_type)?;
24773                        }
24774                        self.write(")");
24775                    }
24776                    Some(DialectType::SingleStore) => {
24777                        // SingleStore uses RECORD(name TYPE, ...) for struct types
24778                        self.write_keyword("RECORD(");
24779                        for (i, field) in fields.iter().enumerate() {
24780                            if i > 0 {
24781                                self.write(", ");
24782                            }
24783                            if !field.name.is_empty() {
24784                                self.write(&field.name);
24785                                self.write(" ");
24786                            }
24787                            self.generate_data_type(&field.data_type)?;
24788                        }
24789                        self.write(")");
24790                    }
24791                    _ => {
24792                        // Hive/Spark always use angle bracket syntax: STRUCT<name: TYPE>
24793                        let force_angle_brackets = matches!(
24794                            self.config.dialect,
24795                            Some(DialectType::Hive)
24796                                | Some(DialectType::Spark)
24797                                | Some(DialectType::Databricks)
24798                        );
24799                        if *nested && !force_angle_brackets {
24800                            self.write_keyword("STRUCT(");
24801                            for (i, field) in fields.iter().enumerate() {
24802                                if i > 0 {
24803                                    self.write(", ");
24804                                }
24805                                if !field.name.is_empty() {
24806                                    self.write(&field.name);
24807                                    self.write(" ");
24808                                }
24809                                self.generate_data_type(&field.data_type)?;
24810                            }
24811                            self.write(")");
24812                        } else {
24813                            self.write_keyword("STRUCT<");
24814                            for (i, field) in fields.iter().enumerate() {
24815                                if i > 0 {
24816                                    self.write(", ");
24817                                }
24818                                if !field.name.is_empty() {
24819                                    // Named field: name TYPE (with configurable separator for Hive)
24820                                    self.write(&field.name);
24821                                    self.write(self.config.struct_field_sep);
24822                                }
24823                                // For anonymous fields, just output the type
24824                                self.generate_data_type(&field.data_type)?;
24825                                // Spark/Databricks: Output COMMENT clause if present
24826                                if let Some(comment) = &field.comment {
24827                                    self.write(" COMMENT '");
24828                                    self.write(comment);
24829                                    self.write("'");
24830                                }
24831                                // BigQuery: Output OPTIONS clause if present
24832                                if !field.options.is_empty() {
24833                                    self.write(" ");
24834                                    self.generate_options_clause(&field.options)?;
24835                                }
24836                            }
24837                            self.write(">");
24838                        }
24839                    }
24840                }
24841            }
24842            DataType::Enum {
24843                values,
24844                assignments,
24845            } => {
24846                // DuckDB ENUM type: ENUM('RED', 'GREEN', 'BLUE')
24847                // ClickHouse: Enum('hello' = 1, 'world' = 2)
24848                if self.config.dialect == Some(DialectType::ClickHouse) {
24849                    self.write("Enum(");
24850                } else {
24851                    self.write_keyword("ENUM(");
24852                }
24853                for (i, val) in values.iter().enumerate() {
24854                    if i > 0 {
24855                        self.write(", ");
24856                    }
24857                    self.write("'");
24858                    self.write(val);
24859                    self.write("'");
24860                    if let Some(Some(assignment)) = assignments.get(i) {
24861                        self.write(" = ");
24862                        self.write(assignment);
24863                    }
24864                }
24865                self.write(")");
24866            }
24867            DataType::Set { values } => {
24868                // MySQL SET type: SET('a', 'b', 'c')
24869                self.write_keyword("SET(");
24870                for (i, val) in values.iter().enumerate() {
24871                    if i > 0 {
24872                        self.write(", ");
24873                    }
24874                    self.write("'");
24875                    self.write(val);
24876                    self.write("'");
24877                }
24878                self.write(")");
24879            }
24880            DataType::Union { fields } => {
24881                // DuckDB UNION type: UNION(num INT, str TEXT)
24882                self.write_keyword("UNION(");
24883                for (i, (name, dt)) in fields.iter().enumerate() {
24884                    if i > 0 {
24885                        self.write(", ");
24886                    }
24887                    if !name.is_empty() {
24888                        self.write(name);
24889                        self.write(" ");
24890                    }
24891                    self.generate_data_type(dt)?;
24892                }
24893                self.write(")");
24894            }
24895            DataType::Nullable { inner } => {
24896                // ClickHouse: Nullable(T), other dialects: just the inner type
24897                if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
24898                    self.write("Nullable(");
24899                    // Suppress inner Nullable wrapping to prevent Nullable(Nullable(...))
24900                    let saved_depth = self.clickhouse_nullable_depth;
24901                    self.clickhouse_nullable_depth = -1;
24902                    self.generate_data_type(inner)?;
24903                    self.clickhouse_nullable_depth = saved_depth;
24904                    self.write(")");
24905                } else {
24906                    // Map ClickHouse-specific custom type names to standard types
24907                    match inner.as_ref() {
24908                        DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME") => {
24909                            self.generate_data_type(&DataType::Timestamp {
24910                                precision: None,
24911                                timezone: false,
24912                            })?;
24913                        }
24914                        _ => {
24915                            self.generate_data_type(inner)?;
24916                        }
24917                    }
24918                }
24919            }
24920            DataType::Custom { name } => {
24921                // Handle dialect-specific type transformations
24922                let name_upper = name.to_ascii_uppercase();
24923                match self.config.dialect {
24924                    Some(DialectType::ClickHouse) => {
24925                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
24926                            (name_upper[..idx].to_string(), &name[idx..])
24927                        } else {
24928                            (name_upper.clone(), "")
24929                        };
24930                        let mapped = match base_upper.as_str() {
24931                            "DATETIME" | "TIMESTAMPTZ" | "TIMESTAMP" | "TIMESTAMPNTZ"
24932                            | "SMALLDATETIME" | "DATETIME2" => "DateTime",
24933                            "DATETIME64" => "DateTime64",
24934                            "DATE32" => "Date32",
24935                            "INT" => "Int32",
24936                            "MEDIUMINT" => "Int32",
24937                            "INT8" => "Int8",
24938                            "INT16" => "Int16",
24939                            "INT32" => "Int32",
24940                            "INT64" => "Int64",
24941                            "INT128" => "Int128",
24942                            "INT256" => "Int256",
24943                            "UINT8" => "UInt8",
24944                            "UINT16" => "UInt16",
24945                            "UINT32" => "UInt32",
24946                            "UINT64" => "UInt64",
24947                            "UINT128" => "UInt128",
24948                            "UINT256" => "UInt256",
24949                            "FLOAT32" => "Float32",
24950                            "FLOAT64" => "Float64",
24951                            "DECIMAL32" => "Decimal32",
24952                            "DECIMAL64" => "Decimal64",
24953                            "DECIMAL128" => "Decimal128",
24954                            "DECIMAL256" => "Decimal256",
24955                            "ENUM" => "Enum",
24956                            "ENUM8" => "Enum8",
24957                            "ENUM16" => "Enum16",
24958                            "FIXEDSTRING" => "FixedString",
24959                            "NESTED" => "Nested",
24960                            "LOWCARDINALITY" => "LowCardinality",
24961                            "NULLABLE" => "Nullable",
24962                            "IPV4" => "IPv4",
24963                            "IPV6" => "IPv6",
24964                            "POINT" => "Point",
24965                            "RING" => "Ring",
24966                            "LINESTRING" => "LineString",
24967                            "MULTILINESTRING" => "MultiLineString",
24968                            "POLYGON" => "Polygon",
24969                            "MULTIPOLYGON" => "MultiPolygon",
24970                            "AGGREGATEFUNCTION" => "AggregateFunction",
24971                            "SIMPLEAGGREGATEFUNCTION" => "SimpleAggregateFunction",
24972                            "DYNAMIC" => "Dynamic",
24973                            _ => "",
24974                        };
24975                        if mapped.is_empty() {
24976                            self.write(name);
24977                        } else {
24978                            self.write(mapped);
24979                            if matches!(base_upper.as_str(), "ENUM8" | "ENUM16")
24980                                && !suffix.is_empty()
24981                            {
24982                                let escaped_suffix = suffix
24983                                    .replace('\\', "\\\\")
24984                                    .replace('\t', "\\t")
24985                                    .replace('\n', "\\n")
24986                                    .replace('\r', "\\r");
24987                                self.write(&escaped_suffix);
24988                            } else {
24989                                self.write(suffix);
24990                            }
24991                        }
24992                    }
24993                    Some(DialectType::MySQL)
24994                        if name_upper == "TIMESTAMPTZ" || name_upper == "TIMESTAMPLTZ" =>
24995                    {
24996                        // MySQL doesn't support TIMESTAMPTZ/TIMESTAMPLTZ, use TIMESTAMP
24997                        self.write_keyword("TIMESTAMP");
24998                    }
24999                    Some(DialectType::Snowflake) => {
25000                        let (base_upper, suffix) = if let Some(idx) = name.find('(') {
25001                            (name_upper[..idx].to_string(), &name[idx..])
25002                        } else {
25003                            (name_upper.clone(), "")
25004                        };
25005
25006                        match base_upper.as_str() {
25007                            "TIMESTAMPNTZ" | "TIMESTAMP_NTZ" => {
25008                                self.write_keyword("TIMESTAMPNTZ");
25009                                self.write(suffix);
25010                            }
25011                            "TIMESTAMPLTZ" | "TIMESTAMP_LTZ" => {
25012                                self.write_keyword("TIMESTAMPLTZ");
25013                                self.write(suffix);
25014                            }
25015                            "TIMESTAMPTZ" | "TIMESTAMP_TZ" => {
25016                                self.write_keyword("TIMESTAMPTZ");
25017                                self.write(suffix);
25018                            }
25019                            _ => self.write(name),
25020                        }
25021                    }
25022                    Some(DialectType::Fabric) => {
25023                        let (base_upper, args_str) = if let Some(idx) = name.find('(') {
25024                            (name_upper[..idx].to_string(), Some(&name[idx..]))
25025                        } else {
25026                            (name_upper.clone(), None)
25027                        };
25028
25029                        match base_upper.as_str() {
25030                            "NVARCHAR" => {
25031                                self.write_keyword("VARCHAR");
25032                                if let Some(args) = args_str {
25033                                    self.write(args);
25034                                }
25035                            }
25036                            "NCHAR" => {
25037                                self.write_keyword("CHAR");
25038                                if let Some(args) = args_str {
25039                                    self.write(args);
25040                                }
25041                            }
25042                            _ => self.write(name),
25043                        }
25044                    }
25045                    Some(DialectType::TSQL) if name_upper == "VARIANT" => {
25046                        self.write_keyword("SQL_VARIANT");
25047                    }
25048                    Some(DialectType::DuckDB) if name_upper == "DECFLOAT" => {
25049                        self.write_keyword("DECIMAL(38, 5)");
25050                    }
25051                    Some(DialectType::Exasol) => {
25052                        // Exasol type mappings for custom types
25053                        match name_upper.as_str() {
25054                            // Binary types → VARCHAR
25055                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => self.write_keyword("VARCHAR"),
25056                            // Text types → VARCHAR (TEXT → LONG VARCHAR is handled by DataType::Text)
25057                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => self.write_keyword("VARCHAR"),
25058                            // Integer types
25059                            "MEDIUMINT" => self.write_keyword("INT"),
25060                            // Decimal types → DECIMAL
25061                            "DECIMAL32" | "DECIMAL64" | "DECIMAL128" | "DECIMAL256" => {
25062                                self.write_keyword("DECIMAL")
25063                            }
25064                            // Timestamp types
25065                            "DATETIME" => self.write_keyword("TIMESTAMP"),
25066                            "TIMESTAMPLTZ" => self.write_keyword("TIMESTAMP WITH LOCAL TIME ZONE"),
25067                            _ => self.write(name),
25068                        }
25069                    }
25070                    Some(DialectType::Dremio) => {
25071                        // Dremio type mappings for custom types
25072                        match name_upper.as_str() {
25073                            "TIMESTAMPNTZ" | "DATETIME" => self.write_keyword("TIMESTAMP"),
25074                            "ARRAY" => self.write_keyword("LIST"),
25075                            "NCHAR" => self.write_keyword("VARCHAR"),
25076                            _ => self.write(name),
25077                        }
25078                    }
25079                    // Map dialect-specific custom types to standard SQL types for other dialects
25080                    _ => {
25081                        // Extract base name and args for types with parenthesized args (e.g., DATETIME2(3))
25082                        let (base_upper, _args_str) = if let Some(idx) = name_upper.find('(') {
25083                            (name_upper[..idx].to_string(), Some(&name[idx..]))
25084                        } else {
25085                            (name_upper.clone(), None)
25086                        };
25087
25088                        match base_upper.as_str() {
25089                            "INT64"
25090                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25091                            {
25092                                self.write_keyword("BIGINT");
25093                            }
25094                            "FLOAT64"
25095                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25096                            {
25097                                self.write_keyword("DOUBLE");
25098                            }
25099                            "BOOL"
25100                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25101                            {
25102                                self.write_keyword("BOOLEAN");
25103                            }
25104                            "BYTES"
25105                                if matches!(
25106                                    self.config.dialect,
25107                                    Some(DialectType::Spark)
25108                                        | Some(DialectType::Hive)
25109                                        | Some(DialectType::Databricks)
25110                                ) =>
25111                            {
25112                                self.write_keyword("BINARY");
25113                            }
25114                            "BYTES"
25115                                if !matches!(self.config.dialect, Some(DialectType::BigQuery)) =>
25116                            {
25117                                self.write_keyword("VARBINARY");
25118                            }
25119                            // TSQL DATETIME2/SMALLDATETIME -> TIMESTAMP
25120                            "DATETIME2" | "SMALLDATETIME"
25121                                if !matches!(
25122                                    self.config.dialect,
25123                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25124                                ) =>
25125                            {
25126                                // PostgreSQL preserves precision, others don't
25127                                if matches!(
25128                                    self.config.dialect,
25129                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
25130                                ) {
25131                                    self.write_keyword("TIMESTAMP");
25132                                    if let Some(args) = _args_str {
25133                                        self.write(args);
25134                                    }
25135                                } else {
25136                                    self.write_keyword("TIMESTAMP");
25137                                }
25138                            }
25139                            // TSQL DATETIMEOFFSET -> TIMESTAMPTZ
25140                            "DATETIMEOFFSET"
25141                                if !matches!(
25142                                    self.config.dialect,
25143                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25144                                ) =>
25145                            {
25146                                if matches!(
25147                                    self.config.dialect,
25148                                    Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
25149                                ) {
25150                                    self.write_keyword("TIMESTAMPTZ");
25151                                    if let Some(args) = _args_str {
25152                                        self.write(args);
25153                                    }
25154                                } else {
25155                                    self.write_keyword("TIMESTAMPTZ");
25156                                }
25157                            }
25158                            // TSQL UNIQUEIDENTIFIER -> UUID or STRING
25159                            "UNIQUEIDENTIFIER"
25160                                if !matches!(
25161                                    self.config.dialect,
25162                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25163                                ) =>
25164                            {
25165                                match self.config.dialect {
25166                                    Some(DialectType::Spark)
25167                                    | Some(DialectType::Databricks)
25168                                    | Some(DialectType::Hive) => self.write_keyword("STRING"),
25169                                    _ => self.write_keyword("UUID"),
25170                                }
25171                            }
25172                            // TSQL BIT -> BOOLEAN for most dialects
25173                            "BIT"
25174                                if !matches!(
25175                                    self.config.dialect,
25176                                    Some(DialectType::TSQL)
25177                                        | Some(DialectType::Fabric)
25178                                        | Some(DialectType::PostgreSQL)
25179                                        | Some(DialectType::MySQL)
25180                                        | Some(DialectType::DuckDB)
25181                                ) =>
25182                            {
25183                                self.write_keyword("BOOLEAN");
25184                            }
25185                            // TSQL NVARCHAR -> VARCHAR (with default size 30 for some dialects)
25186                            "NVARCHAR"
25187                                if !matches!(
25188                                    self.config.dialect,
25189                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25190                                ) =>
25191                            {
25192                                match self.config.dialect {
25193                                    Some(DialectType::Oracle) => {
25194                                        // Oracle: NVARCHAR -> NVARCHAR2
25195                                        self.write_keyword("NVARCHAR2");
25196                                        if let Some(args) = _args_str {
25197                                            self.write(args);
25198                                        }
25199                                    }
25200                                    Some(DialectType::BigQuery) => {
25201                                        // BigQuery: NVARCHAR -> STRING
25202                                        self.write_keyword("STRING");
25203                                    }
25204                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
25205                                        self.write_keyword("TEXT");
25206                                        if let Some(args) = _args_str {
25207                                            self.write(args);
25208                                        }
25209                                    }
25210                                    Some(DialectType::Hive) => {
25211                                        // Hive: NVARCHAR -> STRING
25212                                        self.write_keyword("STRING");
25213                                    }
25214                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
25215                                        if _args_str.is_some() {
25216                                            self.write_keyword("VARCHAR");
25217                                            self.write(_args_str.unwrap());
25218                                        } else {
25219                                            self.write_keyword("STRING");
25220                                        }
25221                                    }
25222                                    _ => {
25223                                        self.write_keyword("VARCHAR");
25224                                        if let Some(args) = _args_str {
25225                                            self.write(args);
25226                                        }
25227                                    }
25228                                }
25229                            }
25230                            // NCHAR -> CHAR (NCHAR for Oracle/TSQL, STRING for BigQuery/Hive)
25231                            "NCHAR"
25232                                if !matches!(
25233                                    self.config.dialect,
25234                                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
25235                                ) =>
25236                            {
25237                                match self.config.dialect {
25238                                    Some(DialectType::Oracle) => {
25239                                        // Oracle natively supports NCHAR
25240                                        self.write_keyword("NCHAR");
25241                                        if let Some(args) = _args_str {
25242                                            self.write(args);
25243                                        }
25244                                    }
25245                                    Some(DialectType::BigQuery) => {
25246                                        // BigQuery: NCHAR -> STRING
25247                                        self.write_keyword("STRING");
25248                                    }
25249                                    Some(DialectType::Hive) => {
25250                                        // Hive: NCHAR -> STRING
25251                                        self.write_keyword("STRING");
25252                                    }
25253                                    Some(DialectType::SQLite) | Some(DialectType::DuckDB) => {
25254                                        self.write_keyword("TEXT");
25255                                        if let Some(args) = _args_str {
25256                                            self.write(args);
25257                                        }
25258                                    }
25259                                    Some(DialectType::Spark) | Some(DialectType::Databricks) => {
25260                                        if _args_str.is_some() {
25261                                            self.write_keyword("CHAR");
25262                                            self.write(_args_str.unwrap());
25263                                        } else {
25264                                            self.write_keyword("STRING");
25265                                        }
25266                                    }
25267                                    _ => {
25268                                        self.write_keyword("CHAR");
25269                                        if let Some(args) = _args_str {
25270                                            self.write(args);
25271                                        }
25272                                    }
25273                                }
25274                            }
25275                            // MySQL text variant types -> map to appropriate target type
25276                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
25277                            "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" => match self.config.dialect {
25278                                Some(DialectType::MySQL)
25279                                | Some(DialectType::SingleStore)
25280                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
25281                                Some(DialectType::Spark)
25282                                | Some(DialectType::Databricks)
25283                                | Some(DialectType::Hive) => self.write_keyword("TEXT"),
25284                                Some(DialectType::BigQuery) => self.write_keyword("STRING"),
25285                                Some(DialectType::Presto)
25286                                | Some(DialectType::Trino)
25287                                | Some(DialectType::Athena) => self.write_keyword("VARCHAR"),
25288                                Some(DialectType::Snowflake)
25289                                | Some(DialectType::Redshift)
25290                                | Some(DialectType::Dremio) => self.write_keyword("VARCHAR"),
25291                                _ => self.write_keyword("TEXT"),
25292                            },
25293                            // MySQL blob variant types -> map to appropriate target type
25294                            // For MySQL/SingleStore: keep original name (column definitions), CAST handling is in generate_cast
25295                            "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => match self.config.dialect {
25296                                Some(DialectType::MySQL)
25297                                | Some(DialectType::SingleStore)
25298                                | Some(DialectType::TiDB) => self.write_keyword(&base_upper),
25299                                Some(DialectType::Spark)
25300                                | Some(DialectType::Databricks)
25301                                | Some(DialectType::Hive) => self.write_keyword("BLOB"),
25302                                Some(DialectType::DuckDB) => self.write_keyword("VARBINARY"),
25303                                Some(DialectType::BigQuery) => self.write_keyword("BYTES"),
25304                                Some(DialectType::Presto)
25305                                | Some(DialectType::Trino)
25306                                | Some(DialectType::Athena) => self.write_keyword("VARBINARY"),
25307                                Some(DialectType::Snowflake)
25308                                | Some(DialectType::Redshift)
25309                                | Some(DialectType::Dremio) => self.write_keyword("VARBINARY"),
25310                                _ => self.write_keyword("BLOB"),
25311                            },
25312                            // LONGVARCHAR -> TEXT for SQLite, VARCHAR for others
25313                            "LONGVARCHAR" => match self.config.dialect {
25314                                Some(DialectType::SQLite) => self.write_keyword("TEXT"),
25315                                _ => self.write_keyword("VARCHAR"),
25316                            },
25317                            // DATETIME -> TIMESTAMP for most, DATETIME for MySQL/Doris/StarRocks/Snowflake
25318                            "DATETIME" => {
25319                                match self.config.dialect {
25320                                    Some(DialectType::MySQL)
25321                                    | Some(DialectType::Doris)
25322                                    | Some(DialectType::StarRocks)
25323                                    | Some(DialectType::TSQL)
25324                                    | Some(DialectType::Fabric)
25325                                    | Some(DialectType::BigQuery)
25326                                    | Some(DialectType::SQLite)
25327                                    | Some(DialectType::Snowflake) => {
25328                                        self.write_keyword("DATETIME");
25329                                        if let Some(args) = _args_str {
25330                                            self.write(args);
25331                                        }
25332                                    }
25333                                    Some(_) => {
25334                                        // Only map to TIMESTAMP when we have a specific target dialect
25335                                        self.write_keyword("TIMESTAMP");
25336                                        if let Some(args) = _args_str {
25337                                            self.write(args);
25338                                        }
25339                                    }
25340                                    None => {
25341                                        // No dialect - preserve original
25342                                        self.write(name);
25343                                    }
25344                                }
25345                            }
25346                            // VARCHAR2/NVARCHAR2 (Oracle) -> VARCHAR for non-Oracle targets
25347                            "VARCHAR2"
25348                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
25349                            {
25350                                match self.config.dialect {
25351                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
25352                                        self.write_keyword("TEXT");
25353                                    }
25354                                    Some(DialectType::Hive)
25355                                    | Some(DialectType::Spark)
25356                                    | Some(DialectType::Databricks)
25357                                    | Some(DialectType::BigQuery)
25358                                    | Some(DialectType::ClickHouse)
25359                                    | Some(DialectType::StarRocks)
25360                                    | Some(DialectType::Doris) => {
25361                                        self.write_keyword("STRING");
25362                                    }
25363                                    _ => {
25364                                        self.write_keyword("VARCHAR");
25365                                        if let Some(args) = _args_str {
25366                                            self.write(args);
25367                                        }
25368                                    }
25369                                }
25370                            }
25371                            "NVARCHAR2"
25372                                if !matches!(self.config.dialect, Some(DialectType::Oracle)) =>
25373                            {
25374                                match self.config.dialect {
25375                                    Some(DialectType::DuckDB) | Some(DialectType::SQLite) => {
25376                                        self.write_keyword("TEXT");
25377                                    }
25378                                    Some(DialectType::Hive)
25379                                    | Some(DialectType::Spark)
25380                                    | Some(DialectType::Databricks)
25381                                    | Some(DialectType::BigQuery)
25382                                    | Some(DialectType::ClickHouse)
25383                                    | Some(DialectType::StarRocks)
25384                                    | Some(DialectType::Doris) => {
25385                                        self.write_keyword("STRING");
25386                                    }
25387                                    _ => {
25388                                        self.write_keyword("VARCHAR");
25389                                        if let Some(args) = _args_str {
25390                                            self.write(args);
25391                                        }
25392                                    }
25393                                }
25394                            }
25395                            _ => self.write(name),
25396                        }
25397                    }
25398                }
25399            }
25400            DataType::Geometry { subtype, srid } => {
25401                // Dialect-specific geometry type mappings
25402                match self.config.dialect {
25403                    Some(DialectType::MySQL) => {
25404                        // MySQL uses POINT SRID 4326 syntax for specific types
25405                        if let Some(sub) = subtype {
25406                            self.write_keyword(sub);
25407                            if let Some(s) = srid {
25408                                self.write(" SRID ");
25409                                self.write(&s.to_string());
25410                            }
25411                        } else {
25412                            self.write_keyword("GEOMETRY");
25413                        }
25414                    }
25415                    Some(DialectType::BigQuery) => {
25416                        // BigQuery only supports GEOGRAPHY, not GEOMETRY
25417                        self.write_keyword("GEOGRAPHY");
25418                    }
25419                    Some(DialectType::Teradata) => {
25420                        // Teradata uses ST_GEOMETRY
25421                        self.write_keyword("ST_GEOMETRY");
25422                        if subtype.is_some() || srid.is_some() {
25423                            self.write("(");
25424                            if let Some(sub) = subtype {
25425                                self.write_keyword(sub);
25426                            }
25427                            if let Some(s) = srid {
25428                                if subtype.is_some() {
25429                                    self.write(", ");
25430                                }
25431                                self.write(&s.to_string());
25432                            }
25433                            self.write(")");
25434                        }
25435                    }
25436                    _ => {
25437                        // PostgreSQL, Snowflake, DuckDB use GEOMETRY(subtype, srid) syntax
25438                        self.write_keyword("GEOMETRY");
25439                        if subtype.is_some() || srid.is_some() {
25440                            self.write("(");
25441                            if let Some(sub) = subtype {
25442                                self.write_keyword(sub);
25443                            }
25444                            if let Some(s) = srid {
25445                                if subtype.is_some() {
25446                                    self.write(", ");
25447                                }
25448                                self.write(&s.to_string());
25449                            }
25450                            self.write(")");
25451                        }
25452                    }
25453                }
25454            }
25455            DataType::Geography { subtype, srid } => {
25456                // Dialect-specific geography type mappings
25457                match self.config.dialect {
25458                    Some(DialectType::MySQL) => {
25459                        // MySQL doesn't have native GEOGRAPHY, use GEOMETRY with SRID 4326
25460                        if let Some(sub) = subtype {
25461                            self.write_keyword(sub);
25462                        } else {
25463                            self.write_keyword("GEOMETRY");
25464                        }
25465                        // Geography implies SRID 4326 (WGS84)
25466                        let effective_srid = srid.unwrap_or(4326);
25467                        self.write(" SRID ");
25468                        self.write(&effective_srid.to_string());
25469                    }
25470                    Some(DialectType::BigQuery) => {
25471                        // BigQuery uses simple GEOGRAPHY without parameters
25472                        self.write_keyword("GEOGRAPHY");
25473                    }
25474                    Some(DialectType::Snowflake) => {
25475                        // Snowflake uses GEOGRAPHY without parameters
25476                        self.write_keyword("GEOGRAPHY");
25477                    }
25478                    _ => {
25479                        // PostgreSQL uses GEOGRAPHY(subtype, srid) syntax
25480                        self.write_keyword("GEOGRAPHY");
25481                        if subtype.is_some() || srid.is_some() {
25482                            self.write("(");
25483                            if let Some(sub) = subtype {
25484                                self.write_keyword(sub);
25485                            }
25486                            if let Some(s) = srid {
25487                                if subtype.is_some() {
25488                                    self.write(", ");
25489                                }
25490                                self.write(&s.to_string());
25491                            }
25492                            self.write(")");
25493                        }
25494                    }
25495                }
25496            }
25497            DataType::CharacterSet { name } => {
25498                // For MySQL CONVERT USING - output as CHAR CHARACTER SET name
25499                self.write_keyword("CHAR CHARACTER SET ");
25500                self.write(name);
25501            }
25502            _ => self.write("UNKNOWN"),
25503        }
25504        Ok(())
25505    }
25506
25507    // === Helper methods ===
25508
25509    #[inline]
25510    fn write(&mut self, s: &str) {
25511        self.output.push_str(s);
25512    }
25513
25514    #[inline]
25515    fn write_space(&mut self) {
25516        self.output.push(' ');
25517    }
25518
25519    #[inline]
25520    fn write_keyword(&mut self, keyword: &str) {
25521        if self.config.uppercase_keywords {
25522            self.output.push_str(keyword);
25523        } else {
25524            for b in keyword.bytes() {
25525                self.output.push(b.to_ascii_lowercase() as char);
25526            }
25527        }
25528    }
25529
25530    /// Write a function name respecting the normalize_functions config setting
25531    fn write_func_name(&mut self, name: &str) {
25532        let normalized = self.normalize_func_name(name);
25533        self.output.push_str(normalized.as_ref());
25534    }
25535
25536    /// Convert strptime format string to Exasol format string
25537    /// Exasol TIME_MAPPING (reverse of Python sqlglot):
25538    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH, %M -> MI, %S -> SS, %a -> DY
25539    fn convert_strptime_to_exasol_format(format: &str) -> String {
25540        let mut result = String::new();
25541        let chars: Vec<char> = format.chars().collect();
25542        let mut i = 0;
25543        while i < chars.len() {
25544            if chars[i] == '%' && i + 1 < chars.len() {
25545                let spec = chars[i + 1];
25546                let exasol_spec = match spec {
25547                    'Y' => "YYYY",
25548                    'y' => "YY",
25549                    'm' => "MM",
25550                    'd' => "DD",
25551                    'H' => "HH",
25552                    'M' => "MI",
25553                    'S' => "SS",
25554                    'a' => "DY",    // abbreviated weekday name
25555                    'A' => "DAY",   // full weekday name
25556                    'b' => "MON",   // abbreviated month name
25557                    'B' => "MONTH", // full month name
25558                    'I' => "H12",   // 12-hour format
25559                    'u' => "ID",    // ISO weekday (1-7)
25560                    'V' => "IW",    // ISO week number
25561                    'G' => "IYYY",  // ISO year
25562                    'W' => "UW",    // Week number (Monday as first day)
25563                    'U' => "UW",    // Week number (Sunday as first day)
25564                    'z' => "Z",     // timezone offset
25565                    _ => {
25566                        // Unknown specifier, keep as-is
25567                        result.push('%');
25568                        result.push(spec);
25569                        i += 2;
25570                        continue;
25571                    }
25572                };
25573                result.push_str(exasol_spec);
25574                i += 2;
25575            } else {
25576                result.push(chars[i]);
25577                i += 1;
25578            }
25579        }
25580        result
25581    }
25582
25583    /// Convert strptime format string to PostgreSQL/Redshift format string
25584    /// PostgreSQL INVERSE_TIME_MAPPING from Python sqlglot:
25585    /// %Y -> YYYY, %y -> YY, %m -> MM, %d -> DD, %H -> HH24, %M -> MI, %S -> SS, %f -> US, etc.
25586    fn convert_strptime_to_postgres_format(format: &str) -> String {
25587        let mut result = String::new();
25588        let chars: Vec<char> = format.chars().collect();
25589        let mut i = 0;
25590        while i < chars.len() {
25591            if chars[i] == '%' && i + 1 < chars.len() {
25592                // Check for %-d, %-m, etc. (non-padded, 3-char sequence)
25593                if chars[i + 1] == '-' && i + 2 < chars.len() {
25594                    let spec = chars[i + 2];
25595                    let pg_spec = match spec {
25596                        'd' => "FMDD",
25597                        'm' => "FMMM",
25598                        'H' => "FMHH24",
25599                        'M' => "FMMI",
25600                        'S' => "FMSS",
25601                        _ => {
25602                            result.push('%');
25603                            result.push('-');
25604                            result.push(spec);
25605                            i += 3;
25606                            continue;
25607                        }
25608                    };
25609                    result.push_str(pg_spec);
25610                    i += 3;
25611                    continue;
25612                }
25613                let spec = chars[i + 1];
25614                let pg_spec = match spec {
25615                    'Y' => "YYYY",
25616                    'y' => "YY",
25617                    'm' => "MM",
25618                    'd' => "DD",
25619                    'H' => "HH24",
25620                    'I' => "HH12",
25621                    'M' => "MI",
25622                    'S' => "SS",
25623                    'f' => "US",      // microseconds
25624                    'u' => "D",       // day of week (1=Monday)
25625                    'j' => "DDD",     // day of year
25626                    'z' => "OF",      // UTC offset
25627                    'Z' => "TZ",      // timezone name
25628                    'A' => "TMDay",   // full weekday name
25629                    'a' => "TMDy",    // abbreviated weekday name
25630                    'b' => "TMMon",   // abbreviated month name
25631                    'B' => "TMMonth", // full month name
25632                    'U' => "WW",      // week number
25633                    _ => {
25634                        // Unknown specifier, keep as-is
25635                        result.push('%');
25636                        result.push(spec);
25637                        i += 2;
25638                        continue;
25639                    }
25640                };
25641                result.push_str(pg_spec);
25642                i += 2;
25643            } else {
25644                result.push(chars[i]);
25645                i += 1;
25646            }
25647        }
25648        result
25649    }
25650
25651    /// Write a LIMIT expression value, evaluating constant expressions if limit_only_literals is set
25652    fn write_limit_expr(&mut self, expr: &Expression) -> Result<()> {
25653        if self.config.limit_only_literals {
25654            if let Some(value) = Self::try_evaluate_constant(expr) {
25655                self.write(&value.to_string());
25656                return Ok(());
25657            }
25658        }
25659        self.generate_expression(expr)
25660    }
25661
25662    /// Format a comment with proper spacing.
25663    /// Converts `/*text*/` to `/* text */` (adding internal spaces if not present).
25664    /// Python SQLGlot normalizes comment format to have spaces inside block comments.
25665    fn write_formatted_comment(&mut self, comment: &str) {
25666        // Normalize all comments to block comment format /* ... */
25667        // This matches Python sqlglot behavior which always outputs block comments
25668        let content = if comment.starts_with("/*") && comment.ends_with("*/") {
25669            // Already block comment - extract inner content
25670            // Preserve internal whitespace, but ensure at least one space padding
25671            &comment[2..comment.len() - 2]
25672        } else if comment.starts_with("--") {
25673            // Line comment - extract content after --
25674            // Preserve internal whitespace (e.g., "--       x" -> "/*       x */")
25675            &comment[2..]
25676        } else {
25677            // Raw content (no delimiters)
25678            comment
25679        };
25680        // Skip empty comments (e.g., bare "--" with no content)
25681        if content.trim().is_empty() {
25682            return;
25683        }
25684        // Escape nested block comment markers to prevent premature closure or unintended nesting.
25685        // This matches Python sqlglot's sanitize_comment behavior.
25686        let sanitized = content.replace("*/", "* /").replace("/*", "/ *");
25687        let content = &sanitized;
25688        // Ensure at least one space after /* and before */
25689        self.output.push_str("/*");
25690        if !content.starts_with(' ') {
25691            self.output.push(' ');
25692        }
25693        self.output.push_str(content);
25694        if !content.ends_with(' ') {
25695            self.output.push(' ');
25696        }
25697        self.output.push_str("*/");
25698    }
25699
25700    /// Escape a raw block content (from dollar-quoted string) for single-quoted output.
25701    /// Escapes single quotes with backslash, and for Snowflake also escapes backslashes.
25702    fn escape_block_for_single_quote(&self, block: &str) -> String {
25703        let escape_backslash = matches!(
25704            self.config.dialect,
25705            Some(crate::dialects::DialectType::Snowflake)
25706        );
25707        let mut escaped = String::with_capacity(block.len() + 4);
25708        for ch in block.chars() {
25709            if ch == '\'' {
25710                escaped.push('\\');
25711                escaped.push('\'');
25712            } else if escape_backslash && ch == '\\' {
25713                escaped.push('\\');
25714                escaped.push('\\');
25715            } else {
25716                escaped.push(ch);
25717            }
25718        }
25719        escaped
25720    }
25721
25722    fn write_newline(&mut self) {
25723        self.output.push('\n');
25724    }
25725
25726    fn write_indent(&mut self) {
25727        for _ in 0..self.indent_level {
25728            self.output.push_str(self.config.indent);
25729        }
25730    }
25731
25732    // === SQLGlot-style pretty printing helpers ===
25733
25734    /// Returns the separator string for pretty printing.
25735    /// Check if the total length of arguments exceeds max_text_width.
25736    /// Used for dynamic line breaking in expressions() formatting.
25737    fn too_wide(&self, args: &[String]) -> bool {
25738        args.iter().map(|s| s.len()).sum::<usize>() > self.config.max_text_width
25739    }
25740
25741    /// Generate an expression to a string using a temporary non-pretty generator.
25742    /// Useful for width calculations before deciding on formatting.
25743    fn generate_to_string(&self, expr: &Expression) -> Result<String> {
25744        let config = GeneratorConfig {
25745            pretty: false,
25746            dialect: self.config.dialect,
25747            ..Default::default()
25748        };
25749        let mut gen = Generator::with_config(config);
25750        gen.generate_expression(expr)?;
25751        Ok(gen.output)
25752    }
25753
25754    /// Writes a clause with a single condition (WHERE, HAVING, QUALIFY).
25755    /// In pretty mode: newline + indented keyword + newline + indented condition
25756    fn write_clause_condition(&mut self, keyword: &str, condition: &Expression) -> Result<()> {
25757        if self.config.pretty {
25758            self.write_newline();
25759            self.write_indent();
25760            self.write_keyword(keyword);
25761            self.write_newline();
25762            self.indent_level += 1;
25763            self.write_indent();
25764            self.generate_expression(condition)?;
25765            self.indent_level -= 1;
25766        } else {
25767            self.write_space();
25768            self.write_keyword(keyword);
25769            self.write_space();
25770            self.generate_expression(condition)?;
25771        }
25772        Ok(())
25773    }
25774
25775    /// Writes a clause with a list of expressions (GROUP BY, DISTRIBUTE BY, CLUSTER BY).
25776    /// In pretty mode: each expression on new line with indentation
25777    fn write_clause_expressions(&mut self, keyword: &str, exprs: &[Expression]) -> Result<()> {
25778        if exprs.is_empty() {
25779            return Ok(());
25780        }
25781
25782        if self.config.pretty {
25783            self.write_newline();
25784            self.write_indent();
25785            self.write_keyword(keyword);
25786            self.write_newline();
25787            self.indent_level += 1;
25788            for (i, expr) in exprs.iter().enumerate() {
25789                if i > 0 {
25790                    self.write(",");
25791                    self.write_newline();
25792                }
25793                self.write_indent();
25794                self.generate_expression(expr)?;
25795            }
25796            self.indent_level -= 1;
25797        } else {
25798            self.write_space();
25799            self.write_keyword(keyword);
25800            self.write_space();
25801            for (i, expr) in exprs.iter().enumerate() {
25802                if i > 0 {
25803                    self.write(", ");
25804                }
25805                self.generate_expression(expr)?;
25806            }
25807        }
25808        Ok(())
25809    }
25810
25811    /// Writes ORDER BY / SORT BY clause with Ordered expressions
25812    fn write_order_clause(&mut self, keyword: &str, orderings: &[Ordered]) -> Result<()> {
25813        if orderings.is_empty() {
25814            return Ok(());
25815        }
25816
25817        if self.config.pretty {
25818            self.write_newline();
25819            self.write_indent();
25820            self.write_keyword(keyword);
25821            self.write_newline();
25822            self.indent_level += 1;
25823            for (i, ordered) in orderings.iter().enumerate() {
25824                if i > 0 {
25825                    self.write(",");
25826                    self.write_newline();
25827                }
25828                self.write_indent();
25829                self.generate_ordered(ordered)?;
25830            }
25831            self.indent_level -= 1;
25832        } else {
25833            self.write_space();
25834            self.write_keyword(keyword);
25835            self.write_space();
25836            for (i, ordered) in orderings.iter().enumerate() {
25837                if i > 0 {
25838                    self.write(", ");
25839                }
25840                self.generate_ordered(ordered)?;
25841            }
25842        }
25843        Ok(())
25844    }
25845
25846    /// Writes WINDOW clause with named window definitions
25847    fn write_window_clause(&mut self, windows: &[NamedWindow]) -> Result<()> {
25848        if windows.is_empty() {
25849            return Ok(());
25850        }
25851
25852        if self.config.pretty {
25853            self.write_newline();
25854            self.write_indent();
25855            self.write_keyword("WINDOW");
25856            self.write_newline();
25857            self.indent_level += 1;
25858            for (i, named_window) in windows.iter().enumerate() {
25859                if i > 0 {
25860                    self.write(",");
25861                    self.write_newline();
25862                }
25863                self.write_indent();
25864                self.generate_identifier(&named_window.name)?;
25865                self.write_space();
25866                self.write_keyword("AS");
25867                self.write(" (");
25868                self.generate_over(&named_window.spec)?;
25869                self.write(")");
25870            }
25871            self.indent_level -= 1;
25872        } else {
25873            self.write_space();
25874            self.write_keyword("WINDOW");
25875            self.write_space();
25876            for (i, named_window) in windows.iter().enumerate() {
25877                if i > 0 {
25878                    self.write(", ");
25879                }
25880                self.generate_identifier(&named_window.name)?;
25881                self.write_space();
25882                self.write_keyword("AS");
25883                self.write(" (");
25884                self.generate_over(&named_window.spec)?;
25885                self.write(")");
25886            }
25887        }
25888        Ok(())
25889    }
25890
25891    // === BATCH-GENERATED STUB METHODS (481 variants) ===
25892    fn generate_ai_agg(&mut self, e: &AIAgg) -> Result<()> {
25893        // AI_AGG(this, expression)
25894        self.write_keyword("AI_AGG");
25895        self.write("(");
25896        self.generate_expression(&e.this)?;
25897        self.write(", ");
25898        self.generate_expression(&e.expression)?;
25899        self.write(")");
25900        Ok(())
25901    }
25902
25903    fn generate_ai_classify(&mut self, e: &AIClassify) -> Result<()> {
25904        // AI_CLASSIFY(input, [categories], [config])
25905        self.write_keyword("AI_CLASSIFY");
25906        self.write("(");
25907        self.generate_expression(&e.this)?;
25908        if let Some(categories) = &e.categories {
25909            self.write(", ");
25910            self.generate_expression(categories)?;
25911        }
25912        if let Some(config) = &e.config {
25913            self.write(", ");
25914            self.generate_expression(config)?;
25915        }
25916        self.write(")");
25917        Ok(())
25918    }
25919
25920    fn generate_add_partition(&mut self, e: &AddPartition) -> Result<()> {
25921        // Python: return f"ADD {exists}{self.sql(expression.this)}{location}"
25922        self.write_keyword("ADD");
25923        self.write_space();
25924        if e.exists {
25925            self.write_keyword("IF NOT EXISTS");
25926            self.write_space();
25927        }
25928        self.generate_expression(&e.this)?;
25929        if let Some(location) = &e.location {
25930            self.write_space();
25931            self.generate_expression(location)?;
25932        }
25933        Ok(())
25934    }
25935
25936    fn generate_algorithm_property(&mut self, e: &AlgorithmProperty) -> Result<()> {
25937        // Python: return f"ALGORITHM={self.sql(expression, 'this')}"
25938        self.write_keyword("ALGORITHM");
25939        self.write("=");
25940        self.generate_expression(&e.this)?;
25941        Ok(())
25942    }
25943
25944    fn generate_aliases(&mut self, e: &Aliases) -> Result<()> {
25945        // Python: return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})"
25946        self.generate_expression(&e.this)?;
25947        self.write_space();
25948        self.write_keyword("AS");
25949        self.write(" (");
25950        for (i, expr) in e.expressions.iter().enumerate() {
25951            if i > 0 {
25952                self.write(", ");
25953            }
25954            self.generate_expression(expr)?;
25955        }
25956        self.write(")");
25957        Ok(())
25958    }
25959
25960    fn generate_allowed_values_property(&mut self, e: &AllowedValuesProperty) -> Result<()> {
25961        // Python: return f"ALLOWED_VALUES {self.expressions(e, flat=True)}"
25962        self.write_keyword("ALLOWED_VALUES");
25963        self.write_space();
25964        for (i, expr) in e.expressions.iter().enumerate() {
25965            if i > 0 {
25966                self.write(", ");
25967            }
25968            self.generate_expression(expr)?;
25969        }
25970        Ok(())
25971    }
25972
25973    fn generate_alter_column(&mut self, e: &AlterColumn) -> Result<()> {
25974        // Python: complex logic based on dtype, default, comment, visible, etc.
25975        self.write_keyword("ALTER COLUMN");
25976        self.write_space();
25977        self.generate_expression(&e.this)?;
25978
25979        if let Some(dtype) = &e.dtype {
25980            self.write_space();
25981            self.write_keyword("SET DATA TYPE");
25982            self.write_space();
25983            self.generate_expression(dtype)?;
25984            if let Some(collate) = &e.collate {
25985                self.write_space();
25986                self.write_keyword("COLLATE");
25987                self.write_space();
25988                self.generate_expression(collate)?;
25989            }
25990            if let Some(using) = &e.using {
25991                self.write_space();
25992                self.write_keyword("USING");
25993                self.write_space();
25994                self.generate_expression(using)?;
25995            }
25996        } else if let Some(default) = &e.default {
25997            self.write_space();
25998            self.write_keyword("SET DEFAULT");
25999            self.write_space();
26000            self.generate_expression(default)?;
26001        } else if let Some(comment) = &e.comment {
26002            self.write_space();
26003            self.write_keyword("COMMENT");
26004            self.write_space();
26005            self.generate_expression(comment)?;
26006        } else if let Some(drop) = &e.drop {
26007            self.write_space();
26008            self.write_keyword("DROP");
26009            self.write_space();
26010            self.generate_expression(drop)?;
26011        } else if let Some(visible) = &e.visible {
26012            self.write_space();
26013            self.generate_expression(visible)?;
26014        } else if let Some(rename_to) = &e.rename_to {
26015            self.write_space();
26016            self.write_keyword("RENAME TO");
26017            self.write_space();
26018            self.generate_expression(rename_to)?;
26019        } else if let Some(allow_null) = &e.allow_null {
26020            self.write_space();
26021            self.generate_expression(allow_null)?;
26022        }
26023        Ok(())
26024    }
26025
26026    fn generate_alter_session(&mut self, e: &AlterSession) -> Result<()> {
26027        // Python: keyword = "UNSET" if expression.args.get("unset") else "SET"; return f"{keyword} {items_sql}"
26028        self.write_keyword("ALTER SESSION");
26029        self.write_space();
26030        if e.unset.is_some() {
26031            self.write_keyword("UNSET");
26032        } else {
26033            self.write_keyword("SET");
26034        }
26035        self.write_space();
26036        for (i, expr) in e.expressions.iter().enumerate() {
26037            if i > 0 {
26038                self.write(", ");
26039            }
26040            self.generate_expression(expr)?;
26041        }
26042        Ok(())
26043    }
26044
26045    fn generate_alter_set(&mut self, e: &AlterSet) -> Result<()> {
26046        // Python (Snowflake): return f"SET{exprs}{file_format}{copy_options}{tag}"
26047        self.write_keyword("SET");
26048
26049        // Generate option (e.g., AUTHORIZATION, LOGGED, UNLOGGED, etc.)
26050        if let Some(opt) = &e.option {
26051            self.write_space();
26052            self.generate_expression(opt)?;
26053        }
26054
26055        // Generate PROPERTIES (for Trino SET PROPERTIES x = y, ...)
26056        // Check if expressions look like property assignments
26057        if !e.expressions.is_empty() {
26058            // Check if this looks like property assignments (for SET PROPERTIES)
26059            let is_properties = e
26060                .expressions
26061                .iter()
26062                .any(|expr| matches!(expr, Expression::Eq(_)));
26063            if is_properties && e.option.is_none() {
26064                self.write_space();
26065                self.write_keyword("PROPERTIES");
26066            }
26067            self.write_space();
26068            for (i, expr) in e.expressions.iter().enumerate() {
26069                if i > 0 {
26070                    self.write(", ");
26071                }
26072                self.generate_expression(expr)?;
26073            }
26074        }
26075
26076        // Generate STAGE_FILE_FORMAT = (...) with space-separated properties
26077        if let Some(file_format) = &e.file_format {
26078            self.write(" ");
26079            self.write_keyword("STAGE_FILE_FORMAT");
26080            self.write(" = (");
26081            self.generate_space_separated_properties(file_format)?;
26082            self.write(")");
26083        }
26084
26085        // Generate STAGE_COPY_OPTIONS = (...) with space-separated properties
26086        if let Some(copy_options) = &e.copy_options {
26087            self.write(" ");
26088            self.write_keyword("STAGE_COPY_OPTIONS");
26089            self.write(" = (");
26090            self.generate_space_separated_properties(copy_options)?;
26091            self.write(")");
26092        }
26093
26094        // Generate TAG ...
26095        if let Some(tag) = &e.tag {
26096            self.write(" ");
26097            self.write_keyword("TAG");
26098            self.write(" ");
26099            self.generate_expression(tag)?;
26100        }
26101
26102        Ok(())
26103    }
26104
26105    /// Generate space-separated properties (for Snowflake STAGE_FILE_FORMAT, etc.)
26106    fn generate_space_separated_properties(&mut self, expr: &Expression) -> Result<()> {
26107        match expr {
26108            Expression::Tuple(t) => {
26109                for (i, prop) in t.expressions.iter().enumerate() {
26110                    if i > 0 {
26111                        self.write(" ");
26112                    }
26113                    self.generate_expression(prop)?;
26114                }
26115            }
26116            _ => {
26117                self.generate_expression(expr)?;
26118            }
26119        }
26120        Ok(())
26121    }
26122
26123    fn generate_alter_sort_key(&mut self, e: &AlterSortKey) -> Result<()> {
26124        // Python: return f"ALTER{compound} SORTKEY {this or expressions}"
26125        self.write_keyword("ALTER");
26126        if e.compound.is_some() {
26127            self.write_space();
26128            self.write_keyword("COMPOUND");
26129        }
26130        self.write_space();
26131        self.write_keyword("SORTKEY");
26132        self.write_space();
26133        if let Some(this) = &e.this {
26134            self.generate_expression(this)?;
26135        } else if !e.expressions.is_empty() {
26136            self.write("(");
26137            for (i, expr) in e.expressions.iter().enumerate() {
26138                if i > 0 {
26139                    self.write(", ");
26140                }
26141                self.generate_expression(expr)?;
26142            }
26143            self.write(")");
26144        }
26145        Ok(())
26146    }
26147
26148    fn generate_analyze(&mut self, e: &Analyze) -> Result<()> {
26149        // Python: return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
26150        self.write_keyword("ANALYZE");
26151        if !e.options.is_empty() {
26152            self.write_space();
26153            for (i, opt) in e.options.iter().enumerate() {
26154                if i > 0 {
26155                    self.write_space();
26156                }
26157                // Write options as keywords (not identifiers) to avoid quoting reserved words like FULL
26158                if let Expression::Identifier(id) = opt {
26159                    self.write_keyword(&id.name);
26160                } else {
26161                    self.generate_expression(opt)?;
26162                }
26163            }
26164        }
26165        if let Some(kind) = &e.kind {
26166            self.write_space();
26167            self.write_keyword(kind);
26168        }
26169        if let Some(this) = &e.this {
26170            self.write_space();
26171            self.generate_expression(this)?;
26172        }
26173        // Column list: ANALYZE tbl(col1, col2) (PostgreSQL)
26174        if !e.columns.is_empty() {
26175            self.write("(");
26176            for (i, col) in e.columns.iter().enumerate() {
26177                if i > 0 {
26178                    self.write(", ");
26179                }
26180                self.write(col);
26181            }
26182            self.write(")");
26183        }
26184        if let Some(partition) = &e.partition {
26185            self.write_space();
26186            self.generate_expression(partition)?;
26187        }
26188        if let Some(mode) = &e.mode {
26189            self.write_space();
26190            self.generate_expression(mode)?;
26191        }
26192        if let Some(expression) = &e.expression {
26193            self.write_space();
26194            self.generate_expression(expression)?;
26195        }
26196        if !e.properties.is_empty() {
26197            self.write_space();
26198            self.write_keyword(self.config.with_properties_prefix);
26199            self.write(" (");
26200            for (i, prop) in e.properties.iter().enumerate() {
26201                if i > 0 {
26202                    self.write(", ");
26203                }
26204                self.generate_expression(prop)?;
26205            }
26206            self.write(")");
26207        }
26208        Ok(())
26209    }
26210
26211    fn generate_analyze_delete(&mut self, e: &AnalyzeDelete) -> Result<()> {
26212        // Python: return f"DELETE{kind} STATISTICS"
26213        self.write_keyword("DELETE");
26214        if let Some(kind) = &e.kind {
26215            self.write_space();
26216            self.write_keyword(kind);
26217        }
26218        self.write_space();
26219        self.write_keyword("STATISTICS");
26220        Ok(())
26221    }
26222
26223    fn generate_analyze_histogram(&mut self, e: &AnalyzeHistogram) -> Result<()> {
26224        // Python: return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
26225        // Write `this` (UPDATE or DROP) as keyword to avoid quoting reserved words
26226        if let Expression::Identifier(id) = e.this.as_ref() {
26227            self.write_keyword(&id.name);
26228        } else {
26229            self.generate_expression(&e.this)?;
26230        }
26231        self.write_space();
26232        self.write_keyword("HISTOGRAM ON");
26233        self.write_space();
26234        for (i, expr) in e.expressions.iter().enumerate() {
26235            if i > 0 {
26236                self.write(", ");
26237            }
26238            self.generate_expression(expr)?;
26239        }
26240        if let Some(expression) = &e.expression {
26241            self.write_space();
26242            self.generate_expression(expression)?;
26243        }
26244        if let Some(update_options) = &e.update_options {
26245            self.write_space();
26246            self.generate_expression(update_options)?;
26247            self.write_space();
26248            self.write_keyword("UPDATE");
26249        }
26250        Ok(())
26251    }
26252
26253    fn generate_analyze_list_chained_rows(&mut self, e: &AnalyzeListChainedRows) -> Result<()> {
26254        // Python: return f"LIST CHAINED ROWS{inner_expression}"
26255        self.write_keyword("LIST CHAINED ROWS");
26256        if let Some(expression) = &e.expression {
26257            self.write_space();
26258            self.write_keyword("INTO");
26259            self.write_space();
26260            self.generate_expression(expression)?;
26261        }
26262        Ok(())
26263    }
26264
26265    fn generate_analyze_sample(&mut self, e: &AnalyzeSample) -> Result<()> {
26266        // Python: return f"SAMPLE {sample} {kind}"
26267        self.write_keyword("SAMPLE");
26268        self.write_space();
26269        if let Some(sample) = &e.sample {
26270            self.generate_expression(sample)?;
26271            self.write_space();
26272        }
26273        self.write_keyword(&e.kind);
26274        Ok(())
26275    }
26276
26277    fn generate_analyze_statistics(&mut self, e: &AnalyzeStatistics) -> Result<()> {
26278        // Python: return f"{kind}{option} STATISTICS{this}{columns}"
26279        self.write_keyword(&e.kind);
26280        if let Some(option) = &e.option {
26281            self.write_space();
26282            self.generate_expression(option)?;
26283        }
26284        self.write_space();
26285        self.write_keyword("STATISTICS");
26286        if let Some(this) = &e.this {
26287            self.write_space();
26288            self.generate_expression(this)?;
26289        }
26290        if !e.expressions.is_empty() {
26291            self.write_space();
26292            for (i, expr) in e.expressions.iter().enumerate() {
26293                if i > 0 {
26294                    self.write(", ");
26295                }
26296                self.generate_expression(expr)?;
26297            }
26298        }
26299        Ok(())
26300    }
26301
26302    fn generate_analyze_validate(&mut self, e: &AnalyzeValidate) -> Result<()> {
26303        // Python: return f"VALIDATE {kind}{this}{inner_expression}"
26304        self.write_keyword("VALIDATE");
26305        self.write_space();
26306        self.write_keyword(&e.kind);
26307        if let Some(this) = &e.this {
26308            self.write_space();
26309            // this is a keyword string like "UPDATE", "CASCADE FAST", etc. - write as keywords
26310            if let Expression::Identifier(id) = this.as_ref() {
26311                self.write_keyword(&id.name);
26312            } else {
26313                self.generate_expression(this)?;
26314            }
26315        }
26316        if let Some(expression) = &e.expression {
26317            self.write_space();
26318            self.write_keyword("INTO");
26319            self.write_space();
26320            self.generate_expression(expression)?;
26321        }
26322        Ok(())
26323    }
26324
26325    fn generate_analyze_with(&mut self, e: &AnalyzeWith) -> Result<()> {
26326        // Python: return f"WITH {expressions}"
26327        self.write_keyword("WITH");
26328        self.write_space();
26329        for (i, expr) in e.expressions.iter().enumerate() {
26330            if i > 0 {
26331                self.write(", ");
26332            }
26333            self.generate_expression(expr)?;
26334        }
26335        Ok(())
26336    }
26337
26338    fn generate_anonymous(&mut self, e: &Anonymous) -> Result<()> {
26339        // Anonymous represents a generic function call: FUNC_NAME(args...)
26340        // Python: return self.func(self.sql(expression, "this"), *expression.expressions)
26341        self.generate_expression(&e.this)?;
26342        self.write("(");
26343        for (i, arg) in e.expressions.iter().enumerate() {
26344            if i > 0 {
26345                self.write(", ");
26346            }
26347            self.generate_expression(arg)?;
26348        }
26349        self.write(")");
26350        Ok(())
26351    }
26352
26353    fn generate_anonymous_agg_func(&mut self, e: &AnonymousAggFunc) -> Result<()> {
26354        // Same as Anonymous but for aggregate functions
26355        self.generate_expression(&e.this)?;
26356        self.write("(");
26357        for (i, arg) in e.expressions.iter().enumerate() {
26358            if i > 0 {
26359                self.write(", ");
26360            }
26361            self.generate_expression(arg)?;
26362        }
26363        self.write(")");
26364        Ok(())
26365    }
26366
26367    fn generate_apply(&mut self, e: &Apply) -> Result<()> {
26368        // Python: return f"{this} APPLY({expr})"
26369        self.generate_expression(&e.this)?;
26370        self.write_space();
26371        self.write_keyword("APPLY");
26372        self.write("(");
26373        self.generate_expression(&e.expression)?;
26374        self.write(")");
26375        Ok(())
26376    }
26377
26378    fn generate_approx_percentile_estimate(&mut self, e: &ApproxPercentileEstimate) -> Result<()> {
26379        // APPROX_PERCENTILE_ESTIMATE(this, percentile)
26380        self.write_keyword("APPROX_PERCENTILE_ESTIMATE");
26381        self.write("(");
26382        self.generate_expression(&e.this)?;
26383        if let Some(percentile) = &e.percentile {
26384            self.write(", ");
26385            self.generate_expression(percentile)?;
26386        }
26387        self.write(")");
26388        Ok(())
26389    }
26390
26391    fn generate_approx_quantile(&mut self, e: &ApproxQuantile) -> Result<()> {
26392        // APPROX_QUANTILE(this, quantile[, accuracy][, weight])
26393        self.write_keyword("APPROX_QUANTILE");
26394        self.write("(");
26395        self.generate_expression(&e.this)?;
26396        if let Some(quantile) = &e.quantile {
26397            self.write(", ");
26398            self.generate_expression(quantile)?;
26399        }
26400        if let Some(accuracy) = &e.accuracy {
26401            self.write(", ");
26402            self.generate_expression(accuracy)?;
26403        }
26404        if let Some(weight) = &e.weight {
26405            self.write(", ");
26406            self.generate_expression(weight)?;
26407        }
26408        self.write(")");
26409        Ok(())
26410    }
26411
26412    fn generate_approx_quantiles(&mut self, e: &ApproxQuantiles) -> Result<()> {
26413        // APPROX_QUANTILES(this, expression)
26414        self.write_keyword("APPROX_QUANTILES");
26415        self.write("(");
26416        self.generate_expression(&e.this)?;
26417        if let Some(expression) = &e.expression {
26418            self.write(", ");
26419            self.generate_expression(expression)?;
26420        }
26421        self.write(")");
26422        Ok(())
26423    }
26424
26425    fn generate_approx_top_k(&mut self, e: &ApproxTopK) -> Result<()> {
26426        // APPROX_TOP_K(this[, expression][, counters])
26427        self.write_keyword("APPROX_TOP_K");
26428        self.write("(");
26429        self.generate_expression(&e.this)?;
26430        if let Some(expression) = &e.expression {
26431            self.write(", ");
26432            self.generate_expression(expression)?;
26433        }
26434        if let Some(counters) = &e.counters {
26435            self.write(", ");
26436            self.generate_expression(counters)?;
26437        }
26438        self.write(")");
26439        Ok(())
26440    }
26441
26442    fn generate_approx_top_k_accumulate(&mut self, e: &ApproxTopKAccumulate) -> Result<()> {
26443        // APPROX_TOP_K_ACCUMULATE(this[, expression])
26444        self.write_keyword("APPROX_TOP_K_ACCUMULATE");
26445        self.write("(");
26446        self.generate_expression(&e.this)?;
26447        if let Some(expression) = &e.expression {
26448            self.write(", ");
26449            self.generate_expression(expression)?;
26450        }
26451        self.write(")");
26452        Ok(())
26453    }
26454
26455    fn generate_approx_top_k_combine(&mut self, e: &ApproxTopKCombine) -> Result<()> {
26456        // APPROX_TOP_K_COMBINE(this[, expression])
26457        self.write_keyword("APPROX_TOP_K_COMBINE");
26458        self.write("(");
26459        self.generate_expression(&e.this)?;
26460        if let Some(expression) = &e.expression {
26461            self.write(", ");
26462            self.generate_expression(expression)?;
26463        }
26464        self.write(")");
26465        Ok(())
26466    }
26467
26468    fn generate_approx_top_k_estimate(&mut self, e: &ApproxTopKEstimate) -> Result<()> {
26469        // APPROX_TOP_K_ESTIMATE(this[, expression])
26470        self.write_keyword("APPROX_TOP_K_ESTIMATE");
26471        self.write("(");
26472        self.generate_expression(&e.this)?;
26473        if let Some(expression) = &e.expression {
26474            self.write(", ");
26475            self.generate_expression(expression)?;
26476        }
26477        self.write(")");
26478        Ok(())
26479    }
26480
26481    fn generate_approx_top_sum(&mut self, e: &ApproxTopSum) -> Result<()> {
26482        // APPROX_TOP_SUM(this, expression[, count])
26483        self.write_keyword("APPROX_TOP_SUM");
26484        self.write("(");
26485        self.generate_expression(&e.this)?;
26486        self.write(", ");
26487        self.generate_expression(&e.expression)?;
26488        if let Some(count) = &e.count {
26489            self.write(", ");
26490            self.generate_expression(count)?;
26491        }
26492        self.write(")");
26493        Ok(())
26494    }
26495
26496    fn generate_arg_max(&mut self, e: &ArgMax) -> Result<()> {
26497        // ARG_MAX(this, expression[, count])
26498        self.write_keyword("ARG_MAX");
26499        self.write("(");
26500        self.generate_expression(&e.this)?;
26501        self.write(", ");
26502        self.generate_expression(&e.expression)?;
26503        if let Some(count) = &e.count {
26504            self.write(", ");
26505            self.generate_expression(count)?;
26506        }
26507        self.write(")");
26508        Ok(())
26509    }
26510
26511    fn generate_arg_min(&mut self, e: &ArgMin) -> Result<()> {
26512        // ARG_MIN(this, expression[, count])
26513        self.write_keyword("ARG_MIN");
26514        self.write("(");
26515        self.generate_expression(&e.this)?;
26516        self.write(", ");
26517        self.generate_expression(&e.expression)?;
26518        if let Some(count) = &e.count {
26519            self.write(", ");
26520            self.generate_expression(count)?;
26521        }
26522        self.write(")");
26523        Ok(())
26524    }
26525
26526    fn generate_array_all(&mut self, e: &ArrayAll) -> Result<()> {
26527        // ARRAY_ALL(this, expression)
26528        self.write_keyword("ARRAY_ALL");
26529        self.write("(");
26530        self.generate_expression(&e.this)?;
26531        self.write(", ");
26532        self.generate_expression(&e.expression)?;
26533        self.write(")");
26534        Ok(())
26535    }
26536
26537    fn generate_array_any(&mut self, e: &ArrayAny) -> Result<()> {
26538        // ARRAY_ANY(this, expression) - fallback implementation
26539        self.write_keyword("ARRAY_ANY");
26540        self.write("(");
26541        self.generate_expression(&e.this)?;
26542        self.write(", ");
26543        self.generate_expression(&e.expression)?;
26544        self.write(")");
26545        Ok(())
26546    }
26547
26548    fn generate_array_construct_compact(&mut self, e: &ArrayConstructCompact) -> Result<()> {
26549        // ARRAY_CONSTRUCT_COMPACT(expressions...)
26550        self.write_keyword("ARRAY_CONSTRUCT_COMPACT");
26551        self.write("(");
26552        for (i, expr) in e.expressions.iter().enumerate() {
26553            if i > 0 {
26554                self.write(", ");
26555            }
26556            self.generate_expression(expr)?;
26557        }
26558        self.write(")");
26559        Ok(())
26560    }
26561
26562    fn generate_array_sum(&mut self, e: &ArraySum) -> Result<()> {
26563        // ARRAY_SUM(this[, expression])
26564        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
26565            self.write("arraySum");
26566        } else {
26567            self.write_keyword("ARRAY_SUM");
26568        }
26569        self.write("(");
26570        self.generate_expression(&e.this)?;
26571        if let Some(expression) = &e.expression {
26572            self.write(", ");
26573            self.generate_expression(expression)?;
26574        }
26575        self.write(")");
26576        Ok(())
26577    }
26578
26579    fn generate_at_index(&mut self, e: &AtIndex) -> Result<()> {
26580        // Python: return f"{this} AT {index}"
26581        self.generate_expression(&e.this)?;
26582        self.write_space();
26583        self.write_keyword("AT");
26584        self.write_space();
26585        self.generate_expression(&e.expression)?;
26586        Ok(())
26587    }
26588
26589    fn generate_attach(&mut self, e: &Attach) -> Result<()> {
26590        // Python: return f"ATTACH{exists_sql} {this}{expressions}"
26591        self.write_keyword("ATTACH");
26592        if e.exists {
26593            self.write_space();
26594            self.write_keyword("IF NOT EXISTS");
26595        }
26596        self.write_space();
26597        self.generate_expression(&e.this)?;
26598        if !e.expressions.is_empty() {
26599            self.write(" (");
26600            for (i, expr) in e.expressions.iter().enumerate() {
26601                if i > 0 {
26602                    self.write(", ");
26603                }
26604                self.generate_expression(expr)?;
26605            }
26606            self.write(")");
26607        }
26608        Ok(())
26609    }
26610
26611    fn generate_attach_option(&mut self, e: &AttachOption) -> Result<()> {
26612        // AttachOption: this [expression]
26613        // Python sqlglot: no equals sign, just space-separated
26614        self.generate_expression(&e.this)?;
26615        if let Some(expression) = &e.expression {
26616            self.write_space();
26617            self.generate_expression(expression)?;
26618        }
26619        Ok(())
26620    }
26621
26622    /// Generate the auto_increment keyword and options for a column definition.
26623    /// Different dialects use different syntax: IDENTITY, AUTOINCREMENT, AUTO_INCREMENT,
26624    /// GENERATED AS IDENTITY, etc.
26625    fn generate_auto_increment_keyword(
26626        &mut self,
26627        col: &crate::expressions::ColumnDef,
26628    ) -> Result<()> {
26629        use crate::dialects::DialectType;
26630        if matches!(self.config.dialect, Some(DialectType::Redshift)) {
26631            self.write_keyword("IDENTITY");
26632            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26633                self.write("(");
26634                if let Some(ref start) = col.auto_increment_start {
26635                    self.generate_expression(start)?;
26636                } else {
26637                    self.write("0");
26638                }
26639                self.write(", ");
26640                if let Some(ref inc) = col.auto_increment_increment {
26641                    self.generate_expression(inc)?;
26642                } else {
26643                    self.write("1");
26644                }
26645                self.write(")");
26646            }
26647        } else if matches!(
26648            self.config.dialect,
26649            Some(DialectType::Snowflake) | Some(DialectType::SQLite)
26650        ) {
26651            self.write_keyword("AUTOINCREMENT");
26652            if let Some(ref start) = col.auto_increment_start {
26653                self.write_space();
26654                self.write_keyword("START");
26655                self.write_space();
26656                self.generate_expression(start)?;
26657            }
26658            if let Some(ref inc) = col.auto_increment_increment {
26659                self.write_space();
26660                self.write_keyword("INCREMENT");
26661                self.write_space();
26662                self.generate_expression(inc)?;
26663            }
26664            if let Some(order) = col.auto_increment_order {
26665                self.write_space();
26666                if order {
26667                    self.write_keyword("ORDER");
26668                } else {
26669                    self.write_keyword("NOORDER");
26670                }
26671            }
26672        } else if matches!(self.config.dialect, Some(DialectType::PostgreSQL)) {
26673            self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
26674            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26675                self.write(" (");
26676                let mut first = true;
26677                if let Some(ref start) = col.auto_increment_start {
26678                    self.write_keyword("START WITH");
26679                    self.write_space();
26680                    self.generate_expression(start)?;
26681                    first = false;
26682                }
26683                if let Some(ref inc) = col.auto_increment_increment {
26684                    if !first {
26685                        self.write_space();
26686                    }
26687                    self.write_keyword("INCREMENT BY");
26688                    self.write_space();
26689                    self.generate_expression(inc)?;
26690                }
26691                self.write(")");
26692            }
26693        } else if matches!(self.config.dialect, Some(DialectType::Databricks)) {
26694            // IDENTITY(start, increment) -> GENERATED BY DEFAULT AS IDENTITY
26695            // Plain IDENTITY/AUTO_INCREMENT -> GENERATED ALWAYS AS IDENTITY
26696            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26697                self.write_keyword("GENERATED BY DEFAULT AS IDENTITY");
26698            } else {
26699                self.write_keyword("GENERATED ALWAYS AS IDENTITY");
26700            }
26701            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26702                self.write(" (");
26703                let mut first = true;
26704                if let Some(ref start) = col.auto_increment_start {
26705                    self.write_keyword("START WITH");
26706                    self.write_space();
26707                    self.generate_expression(start)?;
26708                    first = false;
26709                }
26710                if let Some(ref inc) = col.auto_increment_increment {
26711                    if !first {
26712                        self.write_space();
26713                    }
26714                    self.write_keyword("INCREMENT BY");
26715                    self.write_space();
26716                    self.generate_expression(inc)?;
26717                }
26718                self.write(")");
26719            }
26720        } else if matches!(
26721            self.config.dialect,
26722            Some(DialectType::TSQL) | Some(DialectType::Fabric)
26723        ) {
26724            self.write_keyword("IDENTITY");
26725            if col.auto_increment_start.is_some() || col.auto_increment_increment.is_some() {
26726                self.write("(");
26727                if let Some(ref start) = col.auto_increment_start {
26728                    self.generate_expression(start)?;
26729                } else {
26730                    self.write("0");
26731                }
26732                self.write(", ");
26733                if let Some(ref inc) = col.auto_increment_increment {
26734                    self.generate_expression(inc)?;
26735                } else {
26736                    self.write("1");
26737                }
26738                self.write(")");
26739            }
26740        } else {
26741            self.write_keyword("AUTO_INCREMENT");
26742            if let Some(ref start) = col.auto_increment_start {
26743                self.write_space();
26744                self.write_keyword("START");
26745                self.write_space();
26746                self.generate_expression(start)?;
26747            }
26748            if let Some(ref inc) = col.auto_increment_increment {
26749                self.write_space();
26750                self.write_keyword("INCREMENT");
26751                self.write_space();
26752                self.generate_expression(inc)?;
26753            }
26754            if let Some(order) = col.auto_increment_order {
26755                self.write_space();
26756                if order {
26757                    self.write_keyword("ORDER");
26758                } else {
26759                    self.write_keyword("NOORDER");
26760                }
26761            }
26762        }
26763        Ok(())
26764    }
26765
26766    fn generate_auto_increment_property(&mut self, e: &AutoIncrementProperty) -> Result<()> {
26767        // AUTO_INCREMENT=value
26768        self.write_keyword("AUTO_INCREMENT");
26769        self.write("=");
26770        self.generate_expression(&e.this)?;
26771        Ok(())
26772    }
26773
26774    fn generate_auto_refresh_property(&mut self, e: &AutoRefreshProperty) -> Result<()> {
26775        // AUTO_REFRESH=value
26776        self.write_keyword("AUTO_REFRESH");
26777        self.write("=");
26778        self.generate_expression(&e.this)?;
26779        Ok(())
26780    }
26781
26782    fn generate_backup_property(&mut self, e: &BackupProperty) -> Result<()> {
26783        // BACKUP YES|NO (Redshift syntax uses space, not equals)
26784        self.write_keyword("BACKUP");
26785        self.write_space();
26786        self.generate_expression(&e.this)?;
26787        Ok(())
26788    }
26789
26790    fn generate_base64_decode_binary(&mut self, e: &Base64DecodeBinary) -> Result<()> {
26791        // BASE64_DECODE_BINARY(this[, alphabet])
26792        self.write_keyword("BASE64_DECODE_BINARY");
26793        self.write("(");
26794        self.generate_expression(&e.this)?;
26795        if let Some(alphabet) = &e.alphabet {
26796            self.write(", ");
26797            self.generate_expression(alphabet)?;
26798        }
26799        self.write(")");
26800        Ok(())
26801    }
26802
26803    fn generate_base64_decode_string(&mut self, e: &Base64DecodeString) -> Result<()> {
26804        // BASE64_DECODE_STRING(this[, alphabet])
26805        self.write_keyword("BASE64_DECODE_STRING");
26806        self.write("(");
26807        self.generate_expression(&e.this)?;
26808        if let Some(alphabet) = &e.alphabet {
26809            self.write(", ");
26810            self.generate_expression(alphabet)?;
26811        }
26812        self.write(")");
26813        Ok(())
26814    }
26815
26816    fn generate_base64_encode(&mut self, e: &Base64Encode) -> Result<()> {
26817        // BASE64_ENCODE(this[, max_line_length][, alphabet])
26818        self.write_keyword("BASE64_ENCODE");
26819        self.write("(");
26820        self.generate_expression(&e.this)?;
26821        if let Some(max_line_length) = &e.max_line_length {
26822            self.write(", ");
26823            self.generate_expression(max_line_length)?;
26824        }
26825        if let Some(alphabet) = &e.alphabet {
26826            self.write(", ");
26827            self.generate_expression(alphabet)?;
26828        }
26829        self.write(")");
26830        Ok(())
26831    }
26832
26833    fn generate_block_compression_property(&mut self, e: &BlockCompressionProperty) -> Result<()> {
26834        // BLOCKCOMPRESSION=... (complex Teradata property)
26835        self.write_keyword("BLOCKCOMPRESSION");
26836        self.write("=");
26837        if let Some(autotemp) = &e.autotemp {
26838            self.write_keyword("AUTOTEMP");
26839            self.write("(");
26840            self.generate_expression(autotemp)?;
26841            self.write(")");
26842        }
26843        if let Some(always) = &e.always {
26844            self.generate_expression(always)?;
26845        }
26846        if let Some(default) = &e.default {
26847            self.generate_expression(default)?;
26848        }
26849        if let Some(manual) = &e.manual {
26850            self.generate_expression(manual)?;
26851        }
26852        if let Some(never) = &e.never {
26853            self.generate_expression(never)?;
26854        }
26855        Ok(())
26856    }
26857
26858    fn generate_booland(&mut self, e: &Booland) -> Result<()> {
26859        // Python: return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))"
26860        self.write("((");
26861        self.generate_expression(&e.this)?;
26862        self.write(") ");
26863        self.write_keyword("AND");
26864        self.write(" (");
26865        self.generate_expression(&e.expression)?;
26866        self.write("))");
26867        Ok(())
26868    }
26869
26870    fn generate_boolor(&mut self, e: &Boolor) -> Result<()> {
26871        // Python: return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))"
26872        self.write("((");
26873        self.generate_expression(&e.this)?;
26874        self.write(") ");
26875        self.write_keyword("OR");
26876        self.write(" (");
26877        self.generate_expression(&e.expression)?;
26878        self.write("))");
26879        Ok(())
26880    }
26881
26882    fn generate_build_property(&mut self, e: &BuildProperty) -> Result<()> {
26883        // BUILD value (e.g., BUILD IMMEDIATE, BUILD DEFERRED)
26884        self.write_keyword("BUILD");
26885        self.write_space();
26886        self.generate_expression(&e.this)?;
26887        Ok(())
26888    }
26889
26890    fn generate_byte_string(&mut self, e: &ByteString) -> Result<()> {
26891        // Byte string literal like B'...' or X'...'
26892        self.generate_expression(&e.this)?;
26893        Ok(())
26894    }
26895
26896    fn generate_case_specific_column_constraint(
26897        &mut self,
26898        e: &CaseSpecificColumnConstraint,
26899    ) -> Result<()> {
26900        // CASESPECIFIC or NOT CASESPECIFIC (Teradata)
26901        if e.not_.is_some() {
26902            self.write_keyword("NOT");
26903            self.write_space();
26904        }
26905        self.write_keyword("CASESPECIFIC");
26906        Ok(())
26907    }
26908
26909    fn generate_cast_to_str_type(&mut self, e: &CastToStrType) -> Result<()> {
26910        // Cast to string type (dialect-specific)
26911        self.write_keyword("CAST");
26912        self.write("(");
26913        self.generate_expression(&e.this)?;
26914        if self.config.dialect == Some(DialectType::ClickHouse) {
26915            // ClickHouse: CAST(expr, 'type_string')
26916            self.write(", ");
26917        } else {
26918            self.write_space();
26919            self.write_keyword("AS");
26920            self.write_space();
26921        }
26922        if let Some(to) = &e.to {
26923            self.generate_expression(to)?;
26924        }
26925        self.write(")");
26926        Ok(())
26927    }
26928
26929    fn generate_changes(&mut self, e: &Changes) -> Result<()> {
26930        // CHANGES (INFORMATION => value) AT|BEFORE (...) END (...)
26931        // Python: f"CHANGES ({information}){at_before}{end}"
26932        self.write_keyword("CHANGES");
26933        self.write(" (");
26934        if let Some(information) = &e.information {
26935            self.write_keyword("INFORMATION");
26936            self.write(" => ");
26937            self.generate_expression(information)?;
26938        }
26939        self.write(")");
26940        // at_before and end are HistoricalData expressions that generate their own keywords
26941        if let Some(at_before) = &e.at_before {
26942            self.write(" ");
26943            self.generate_expression(at_before)?;
26944        }
26945        if let Some(end) = &e.end {
26946            self.write(" ");
26947            self.generate_expression(end)?;
26948        }
26949        Ok(())
26950    }
26951
26952    fn generate_character_set_column_constraint(
26953        &mut self,
26954        e: &CharacterSetColumnConstraint,
26955    ) -> Result<()> {
26956        // CHARACTER SET charset_name
26957        self.write_keyword("CHARACTER SET");
26958        self.write_space();
26959        self.generate_expression(&e.this)?;
26960        Ok(())
26961    }
26962
26963    fn generate_character_set_property(&mut self, e: &CharacterSetProperty) -> Result<()> {
26964        // [DEFAULT] CHARACTER SET=value
26965        if e.default.is_some() {
26966            self.write_keyword("DEFAULT");
26967            self.write_space();
26968        }
26969        self.write_keyword("CHARACTER SET");
26970        self.write("=");
26971        self.generate_expression(&e.this)?;
26972        Ok(())
26973    }
26974
26975    fn generate_check_column_constraint(&mut self, e: &CheckColumnConstraint) -> Result<()> {
26976        // Python: return f"CHECK ({self.sql(expression, 'this')}){enforced}"
26977        self.write_keyword("CHECK");
26978        self.write(" (");
26979        self.generate_expression(&e.this)?;
26980        self.write(")");
26981        if e.enforced.is_some() {
26982            self.write_space();
26983            self.write_keyword("ENFORCED");
26984        }
26985        Ok(())
26986    }
26987
26988    fn generate_assume_column_constraint(&mut self, e: &AssumeColumnConstraint) -> Result<()> {
26989        // Python: return f"ASSUME ({self.sql(e, 'this')})"
26990        self.write_keyword("ASSUME");
26991        self.write(" (");
26992        self.generate_expression(&e.this)?;
26993        self.write(")");
26994        Ok(())
26995    }
26996
26997    fn generate_check_json(&mut self, e: &CheckJson) -> Result<()> {
26998        // CHECK_JSON(this)
26999        self.write_keyword("CHECK_JSON");
27000        self.write("(");
27001        self.generate_expression(&e.this)?;
27002        self.write(")");
27003        Ok(())
27004    }
27005
27006    fn generate_check_xml(&mut self, e: &CheckXml) -> Result<()> {
27007        // CHECK_XML(this)
27008        self.write_keyword("CHECK_XML");
27009        self.write("(");
27010        self.generate_expression(&e.this)?;
27011        self.write(")");
27012        Ok(())
27013    }
27014
27015    fn generate_checksum_property(&mut self, e: &ChecksumProperty) -> Result<()> {
27016        // CHECKSUM=[ON|OFF|DEFAULT]
27017        self.write_keyword("CHECKSUM");
27018        self.write("=");
27019        if e.on.is_some() {
27020            self.write_keyword("ON");
27021        } else if e.default.is_some() {
27022            self.write_keyword("DEFAULT");
27023        } else {
27024            self.write_keyword("OFF");
27025        }
27026        Ok(())
27027    }
27028
27029    fn generate_clone(&mut self, e: &Clone) -> Result<()> {
27030        // Python: return f"{shallow}{keyword} {this}"
27031        if e.shallow.is_some() {
27032            self.write_keyword("SHALLOW");
27033            self.write_space();
27034        }
27035        if e.copy.is_some() {
27036            self.write_keyword("COPY");
27037        } else {
27038            self.write_keyword("CLONE");
27039        }
27040        self.write_space();
27041        self.generate_expression(&e.this)?;
27042        Ok(())
27043    }
27044
27045    fn generate_cluster_by(&mut self, e: &ClusterBy) -> Result<()> {
27046        // CLUSTER BY (expressions)
27047        self.write_keyword("CLUSTER BY");
27048        self.write(" (");
27049        for (i, ord) in e.expressions.iter().enumerate() {
27050            if i > 0 {
27051                self.write(", ");
27052            }
27053            self.generate_ordered(ord)?;
27054        }
27055        self.write(")");
27056        Ok(())
27057    }
27058
27059    fn generate_cluster_by_columns_property(&mut self, e: &ClusterByColumnsProperty) -> Result<()> {
27060        // BigQuery table property: CLUSTER BY col1, col2
27061        self.write_keyword("CLUSTER BY");
27062        self.write_space();
27063        for (i, col) in e.columns.iter().enumerate() {
27064            if i > 0 {
27065                self.write(", ");
27066            }
27067            self.generate_identifier(col)?;
27068        }
27069        Ok(())
27070    }
27071
27072    fn generate_clustered_by_property(&mut self, e: &ClusteredByProperty) -> Result<()> {
27073        // Python: return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
27074        self.write_keyword("CLUSTERED BY");
27075        self.write(" (");
27076        for (i, expr) in e.expressions.iter().enumerate() {
27077            if i > 0 {
27078                self.write(", ");
27079            }
27080            self.generate_expression(expr)?;
27081        }
27082        self.write(")");
27083        if let Some(sorted_by) = &e.sorted_by {
27084            self.write_space();
27085            self.write_keyword("SORTED BY");
27086            self.write(" (");
27087            // Unwrap Tuple to avoid double parentheses
27088            if let Expression::Tuple(t) = sorted_by.as_ref() {
27089                for (i, expr) in t.expressions.iter().enumerate() {
27090                    if i > 0 {
27091                        self.write(", ");
27092                    }
27093                    self.generate_expression(expr)?;
27094                }
27095            } else {
27096                self.generate_expression(sorted_by)?;
27097            }
27098            self.write(")");
27099        }
27100        if let Some(buckets) = &e.buckets {
27101            self.write_space();
27102            self.write_keyword("INTO");
27103            self.write_space();
27104            self.generate_expression(buckets)?;
27105            self.write_space();
27106            self.write_keyword("BUCKETS");
27107        }
27108        Ok(())
27109    }
27110
27111    fn generate_collate_property(&mut self, e: &CollateProperty) -> Result<()> {
27112        // [DEFAULT] COLLATE [=] value
27113        // BigQuery uses space: DEFAULT COLLATE 'en'
27114        // Others use equals: COLLATE='en'
27115        if e.default.is_some() {
27116            self.write_keyword("DEFAULT");
27117            self.write_space();
27118        }
27119        self.write_keyword("COLLATE");
27120        // BigQuery uses space between COLLATE and value
27121        match self.config.dialect {
27122            Some(DialectType::BigQuery) => self.write_space(),
27123            _ => self.write("="),
27124        }
27125        self.generate_expression(&e.this)?;
27126        Ok(())
27127    }
27128
27129    fn generate_column_constraint(&mut self, e: &ColumnConstraint) -> Result<()> {
27130        // ColumnConstraint is an enum
27131        match e {
27132            ColumnConstraint::NotNull => {
27133                self.write_keyword("NOT NULL");
27134            }
27135            ColumnConstraint::Null => {
27136                self.write_keyword("NULL");
27137            }
27138            ColumnConstraint::Unique => {
27139                self.write_keyword("UNIQUE");
27140            }
27141            ColumnConstraint::PrimaryKey => {
27142                self.write_keyword("PRIMARY KEY");
27143            }
27144            ColumnConstraint::Default(expr) => {
27145                self.write_keyword("DEFAULT");
27146                self.write_space();
27147                self.generate_expression(expr)?;
27148            }
27149            ColumnConstraint::Check(expr) => {
27150                self.write_keyword("CHECK");
27151                self.write(" (");
27152                self.generate_expression(expr)?;
27153                self.write(")");
27154            }
27155            ColumnConstraint::References(fk_ref) => {
27156                if fk_ref.has_foreign_key_keywords {
27157                    self.write_keyword("FOREIGN KEY");
27158                    self.write_space();
27159                }
27160                self.write_keyword("REFERENCES");
27161                self.write_space();
27162                self.generate_table(&fk_ref.table)?;
27163                if !fk_ref.columns.is_empty() {
27164                    self.write(" (");
27165                    for (i, col) in fk_ref.columns.iter().enumerate() {
27166                        if i > 0 {
27167                            self.write(", ");
27168                        }
27169                        self.generate_identifier(col)?;
27170                    }
27171                    self.write(")");
27172                }
27173            }
27174            ColumnConstraint::GeneratedAsIdentity(gen) => {
27175                self.write_keyword("GENERATED");
27176                self.write_space();
27177                if gen.always {
27178                    self.write_keyword("ALWAYS");
27179                } else {
27180                    self.write_keyword("BY DEFAULT");
27181                    if gen.on_null {
27182                        self.write_space();
27183                        self.write_keyword("ON NULL");
27184                    }
27185                }
27186                self.write_space();
27187                self.write_keyword("AS IDENTITY");
27188            }
27189            ColumnConstraint::Collate(collation) => {
27190                self.write_keyword("COLLATE");
27191                self.write_space();
27192                self.generate_identifier(collation)?;
27193            }
27194            ColumnConstraint::Comment(comment) => {
27195                self.write_keyword("COMMENT");
27196                self.write(" '");
27197                self.write(comment);
27198                self.write("'");
27199            }
27200            ColumnConstraint::ComputedColumn(cc) => {
27201                self.generate_computed_column_inline(cc)?;
27202            }
27203            ColumnConstraint::GeneratedAsRow(gar) => {
27204                self.generate_generated_as_row_inline(gar)?;
27205            }
27206            ColumnConstraint::Tags(tags) => {
27207                self.write_keyword("TAG");
27208                self.write(" (");
27209                for (i, expr) in tags.expressions.iter().enumerate() {
27210                    if i > 0 {
27211                        self.write(", ");
27212                    }
27213                    self.generate_expression(expr)?;
27214                }
27215                self.write(")");
27216            }
27217            ColumnConstraint::Path(path_expr) => {
27218                self.write_keyword("PATH");
27219                self.write_space();
27220                self.generate_expression(path_expr)?;
27221            }
27222        }
27223        Ok(())
27224    }
27225
27226    fn generate_column_position(&mut self, e: &ColumnPosition) -> Result<()> {
27227        // ColumnPosition is an enum
27228        match e {
27229            ColumnPosition::First => {
27230                self.write_keyword("FIRST");
27231            }
27232            ColumnPosition::After(ident) => {
27233                self.write_keyword("AFTER");
27234                self.write_space();
27235                self.generate_identifier(ident)?;
27236            }
27237        }
27238        Ok(())
27239    }
27240
27241    fn generate_column_prefix(&mut self, e: &ColumnPrefix) -> Result<()> {
27242        // column(prefix)
27243        self.generate_expression(&e.this)?;
27244        self.write("(");
27245        self.generate_expression(&e.expression)?;
27246        self.write(")");
27247        Ok(())
27248    }
27249
27250    fn generate_columns(&mut self, e: &Columns) -> Result<()> {
27251        // If unpack is true, this came from * COLUMNS(pattern)
27252        // DuckDB syntax: * COLUMNS(c ILIKE '%suffix') or COLUMNS(pattern)
27253        if let Some(ref unpack) = e.unpack {
27254            if let Expression::Boolean(b) = unpack.as_ref() {
27255                if b.value {
27256                    self.write("*");
27257                }
27258            }
27259        }
27260        self.write_keyword("COLUMNS");
27261        self.write("(");
27262        self.generate_expression(&e.this)?;
27263        self.write(")");
27264        Ok(())
27265    }
27266
27267    fn generate_combined_agg_func(&mut self, e: &CombinedAggFunc) -> Result<()> {
27268        // Combined aggregate: FUNC(args) combined
27269        self.generate_expression(&e.this)?;
27270        self.write("(");
27271        for (i, expr) in e.expressions.iter().enumerate() {
27272            if i > 0 {
27273                self.write(", ");
27274            }
27275            self.generate_expression(expr)?;
27276        }
27277        self.write(")");
27278        Ok(())
27279    }
27280
27281    fn generate_combined_parameterized_agg(&mut self, e: &CombinedParameterizedAgg) -> Result<()> {
27282        // Combined parameterized aggregate: FUNC(params)(expressions)
27283        self.generate_expression(&e.this)?;
27284        self.write("(");
27285        for (i, param) in e.params.iter().enumerate() {
27286            if i > 0 {
27287                self.write(", ");
27288            }
27289            self.generate_expression(param)?;
27290        }
27291        self.write(")(");
27292        for (i, expr) in e.expressions.iter().enumerate() {
27293            if i > 0 {
27294                self.write(", ");
27295            }
27296            self.generate_expression(expr)?;
27297        }
27298        self.write(")");
27299        Ok(())
27300    }
27301
27302    fn generate_commit(&mut self, e: &Commit) -> Result<()> {
27303        // COMMIT [TRANSACTION [transaction_name]] [WITH (DELAYED_DURABILITY = ON|OFF)] [AND [NO] CHAIN]
27304        self.write_keyword("COMMIT");
27305
27306        // TSQL always uses COMMIT TRANSACTION
27307        if e.this.is_none()
27308            && matches!(
27309                self.config.dialect,
27310                Some(DialectType::TSQL) | Some(DialectType::Fabric)
27311            )
27312        {
27313            self.write_space();
27314            self.write_keyword("TRANSACTION");
27315        }
27316
27317        // Check if this has TRANSACTION keyword or transaction name
27318        if let Some(this) = &e.this {
27319            // Check if it's just the "TRANSACTION" marker or an actual transaction name
27320            let is_transaction_marker = matches!(
27321                this.as_ref(),
27322                Expression::Identifier(id) if id.name == "TRANSACTION"
27323            );
27324
27325            self.write_space();
27326            self.write_keyword("TRANSACTION");
27327
27328            // If it's a real transaction name, output it
27329            if !is_transaction_marker {
27330                self.write_space();
27331                self.generate_expression(this)?;
27332            }
27333        }
27334
27335        // Output WITH (DELAYED_DURABILITY = ON|OFF) for TSQL
27336        if let Some(durability) = &e.durability {
27337            self.write_space();
27338            self.write_keyword("WITH");
27339            self.write(" (");
27340            self.write_keyword("DELAYED_DURABILITY");
27341            self.write(" = ");
27342            if let Expression::Boolean(BooleanLiteral { value: true }) = durability.as_ref() {
27343                self.write_keyword("ON");
27344            } else {
27345                self.write_keyword("OFF");
27346            }
27347            self.write(")");
27348        }
27349
27350        // Output AND [NO] CHAIN
27351        if let Some(chain) = &e.chain {
27352            self.write_space();
27353            if let Expression::Boolean(BooleanLiteral { value: false }) = chain.as_ref() {
27354                self.write_keyword("AND NO CHAIN");
27355            } else {
27356                self.write_keyword("AND CHAIN");
27357            }
27358        }
27359        Ok(())
27360    }
27361
27362    fn generate_comprehension(&mut self, e: &Comprehension) -> Result<()> {
27363        // Python-style comprehension: [expr FOR var[, pos] IN iterator IF condition]
27364        self.write("[");
27365        self.generate_expression(&e.this)?;
27366        self.write_space();
27367        self.write_keyword("FOR");
27368        self.write_space();
27369        self.generate_expression(&e.expression)?;
27370        // Handle optional position variable (for enumerate-like syntax)
27371        if let Some(pos) = &e.position {
27372            self.write(", ");
27373            self.generate_expression(pos)?;
27374        }
27375        if let Some(iterator) = &e.iterator {
27376            self.write_space();
27377            self.write_keyword("IN");
27378            self.write_space();
27379            self.generate_expression(iterator)?;
27380        }
27381        if let Some(condition) = &e.condition {
27382            self.write_space();
27383            self.write_keyword("IF");
27384            self.write_space();
27385            self.generate_expression(condition)?;
27386        }
27387        self.write("]");
27388        Ok(())
27389    }
27390
27391    fn generate_compress(&mut self, e: &Compress) -> Result<()> {
27392        // COMPRESS(this[, method])
27393        self.write_keyword("COMPRESS");
27394        self.write("(");
27395        self.generate_expression(&e.this)?;
27396        if let Some(method) = &e.method {
27397            self.write(", '");
27398            self.write(method);
27399            self.write("'");
27400        }
27401        self.write(")");
27402        Ok(())
27403    }
27404
27405    fn generate_compress_column_constraint(&mut self, e: &CompressColumnConstraint) -> Result<()> {
27406        // Python: return f"COMPRESS {this}"
27407        self.write_keyword("COMPRESS");
27408        if let Some(this) = &e.this {
27409            self.write_space();
27410            self.generate_expression(this)?;
27411        }
27412        Ok(())
27413    }
27414
27415    fn generate_computed_column_constraint(&mut self, e: &ComputedColumnConstraint) -> Result<()> {
27416        // Python: return f"AS {this}{persisted}"
27417        self.write_keyword("AS");
27418        self.write_space();
27419        self.generate_expression(&e.this)?;
27420        if e.not_null.is_some() {
27421            self.write_space();
27422            self.write_keyword("PERSISTED NOT NULL");
27423        } else if e.persisted.is_some() {
27424            self.write_space();
27425            self.write_keyword("PERSISTED");
27426        }
27427        Ok(())
27428    }
27429
27430    /// Generate a ComputedColumn constraint inline within a column definition.
27431    /// Handles MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
27432    /// Handles TSQL: AS (expr) [PERSISTED] [NOT NULL]
27433    fn generate_computed_column_inline(&mut self, cc: &ComputedColumn) -> Result<()> {
27434        let computed_expr = if matches!(
27435            self.config.dialect,
27436            Some(DialectType::TSQL) | Some(DialectType::Fabric)
27437        ) {
27438            match &*cc.expression {
27439                Expression::Year(y) if !matches!(&y.this, Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
27440                {
27441                    let wrapped = Expression::Cast(Box::new(Cast {
27442                        this: y.this.clone(),
27443                        to: DataType::Date,
27444                        trailing_comments: Vec::new(),
27445                        double_colon_syntax: false,
27446                        format: None,
27447                        default: None,
27448                        inferred_type: None,
27449                    }));
27450                    Expression::Year(Box::new(UnaryFunc::new(wrapped)))
27451                }
27452                Expression::Function(f)
27453                    if f.name.eq_ignore_ascii_case("YEAR")
27454                        && f.args.len() == 1
27455                        && !matches!(&f.args[0], Expression::Cast(c) if matches!(c.to, DataType::Date)) =>
27456                {
27457                    let wrapped = Expression::Cast(Box::new(Cast {
27458                        this: f.args[0].clone(),
27459                        to: DataType::Date,
27460                        trailing_comments: Vec::new(),
27461                        double_colon_syntax: false,
27462                        format: None,
27463                        default: None,
27464                        inferred_type: None,
27465                    }));
27466                    Expression::Function(Box::new(Function::new("YEAR".to_string(), vec![wrapped])))
27467                }
27468                _ => *cc.expression.clone(),
27469            }
27470        } else {
27471            *cc.expression.clone()
27472        };
27473
27474        match cc.persistence_kind.as_deref() {
27475            Some("STORED") | Some("VIRTUAL") => {
27476                // MySQL/PostgreSQL: GENERATED ALWAYS AS (expr) STORED|VIRTUAL
27477                self.write_keyword("GENERATED ALWAYS AS");
27478                self.write(" (");
27479                self.generate_expression(&computed_expr)?;
27480                self.write(")");
27481                self.write_space();
27482                if cc.persisted {
27483                    self.write_keyword("STORED");
27484                } else {
27485                    self.write_keyword("VIRTUAL");
27486                }
27487            }
27488            Some("PERSISTED") => {
27489                // TSQL/SingleStore: AS (expr) PERSISTED [TYPE] [NOT NULL]
27490                self.write_keyword("AS");
27491                self.write(" (");
27492                self.generate_expression(&computed_expr)?;
27493                self.write(")");
27494                self.write_space();
27495                self.write_keyword("PERSISTED");
27496                // Output data type if present (SingleStore: PERSISTED TYPE NOT NULL)
27497                if let Some(ref dt) = cc.data_type {
27498                    self.write_space();
27499                    self.generate_data_type(dt)?;
27500                }
27501                if cc.not_null {
27502                    self.write_space();
27503                    self.write_keyword("NOT NULL");
27504                }
27505            }
27506            _ => {
27507                // Spark/Databricks/Hive: GENERATED ALWAYS AS (expr)
27508                // TSQL computed column without PERSISTED: AS (expr)
27509                if matches!(
27510                    self.config.dialect,
27511                    Some(DialectType::Spark)
27512                        | Some(DialectType::Databricks)
27513                        | Some(DialectType::Hive)
27514                ) {
27515                    self.write_keyword("GENERATED ALWAYS AS");
27516                    self.write(" (");
27517                    self.generate_expression(&computed_expr)?;
27518                    self.write(")");
27519                } else if matches!(
27520                    self.config.dialect,
27521                    Some(DialectType::TSQL) | Some(DialectType::Fabric)
27522                ) {
27523                    self.write_keyword("AS");
27524                    let omit_parens = matches!(computed_expr, Expression::Year(_))
27525                        || matches!(&computed_expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("YEAR"));
27526                    if omit_parens {
27527                        self.write_space();
27528                        self.generate_expression(&computed_expr)?;
27529                    } else {
27530                        self.write(" (");
27531                        self.generate_expression(&computed_expr)?;
27532                        self.write(")");
27533                    }
27534                } else {
27535                    self.write_keyword("AS");
27536                    self.write(" (");
27537                    self.generate_expression(&computed_expr)?;
27538                    self.write(")");
27539                }
27540            }
27541        }
27542        Ok(())
27543    }
27544
27545    /// Generate a GeneratedAsRow constraint inline within a column definition.
27546    /// TSQL temporal: GENERATED ALWAYS AS ROW START|END [HIDDEN]
27547    fn generate_generated_as_row_inline(&mut self, gar: &GeneratedAsRow) -> Result<()> {
27548        self.write_keyword("GENERATED ALWAYS AS ROW ");
27549        if gar.start {
27550            self.write_keyword("START");
27551        } else {
27552            self.write_keyword("END");
27553        }
27554        if gar.hidden {
27555            self.write_space();
27556            self.write_keyword("HIDDEN");
27557        }
27558        Ok(())
27559    }
27560
27561    /// Generate just the SYSTEM_VERSIONING=ON(...) content without WITH() wrapper.
27562    fn generate_system_versioning_content(
27563        &mut self,
27564        e: &WithSystemVersioningProperty,
27565    ) -> Result<()> {
27566        let mut parts = Vec::new();
27567
27568        if let Some(this) = &e.this {
27569            let mut s = String::from("HISTORY_TABLE=");
27570            let mut gen = Generator::with_arc_config(self.config.clone());
27571            gen.generate_expression(this)?;
27572            s.push_str(&gen.output);
27573            parts.push(s);
27574        }
27575
27576        if let Some(data_consistency) = &e.data_consistency {
27577            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
27578            let mut gen = Generator::with_arc_config(self.config.clone());
27579            gen.generate_expression(data_consistency)?;
27580            s.push_str(&gen.output);
27581            parts.push(s);
27582        }
27583
27584        if let Some(retention_period) = &e.retention_period {
27585            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
27586            let mut gen = Generator::with_arc_config(self.config.clone());
27587            gen.generate_expression(retention_period)?;
27588            s.push_str(&gen.output);
27589            parts.push(s);
27590        }
27591
27592        self.write_keyword("SYSTEM_VERSIONING");
27593        self.write("=");
27594
27595        if !parts.is_empty() {
27596            self.write_keyword("ON");
27597            self.write("(");
27598            self.write(&parts.join(", "));
27599            self.write(")");
27600        } else if e.on.is_some() {
27601            self.write_keyword("ON");
27602        } else {
27603            self.write_keyword("OFF");
27604        }
27605
27606        Ok(())
27607    }
27608
27609    fn generate_conditional_insert(&mut self, e: &ConditionalInsert) -> Result<()> {
27610        // Conditional INSERT for multi-table inserts
27611        // Output: [WHEN cond THEN | ELSE] INTO table [(cols)] [VALUES (...)]
27612        if e.else_.is_some() {
27613            self.write_keyword("ELSE");
27614            self.write_space();
27615        } else if let Some(expression) = &e.expression {
27616            self.write_keyword("WHEN");
27617            self.write_space();
27618            self.generate_expression(expression)?;
27619            self.write_space();
27620            self.write_keyword("THEN");
27621            self.write_space();
27622        }
27623
27624        // Handle Insert expression specially - output "INTO table (cols) VALUES (...)"
27625        // without the "INSERT " prefix
27626        if let Expression::Insert(insert) = e.this.as_ref() {
27627            self.write_keyword("INTO");
27628            self.write_space();
27629            self.generate_table(&insert.table)?;
27630
27631            // Optional column list
27632            if !insert.columns.is_empty() {
27633                self.write(" (");
27634                for (i, col) in insert.columns.iter().enumerate() {
27635                    if i > 0 {
27636                        self.write(", ");
27637                    }
27638                    self.generate_identifier(col)?;
27639                }
27640                self.write(")");
27641            }
27642
27643            // Optional VALUES clause
27644            if !insert.values.is_empty() {
27645                self.write_space();
27646                self.write_keyword("VALUES");
27647                for (row_idx, row) in insert.values.iter().enumerate() {
27648                    if row_idx > 0 {
27649                        self.write(", ");
27650                    }
27651                    self.write(" (");
27652                    for (i, val) in row.iter().enumerate() {
27653                        if i > 0 {
27654                            self.write(", ");
27655                        }
27656                        self.generate_expression(val)?;
27657                    }
27658                    self.write(")");
27659                }
27660            }
27661        } else {
27662            // Fallback for non-Insert expressions
27663            self.generate_expression(&e.this)?;
27664        }
27665        Ok(())
27666    }
27667
27668    fn generate_constraint(&mut self, e: &Constraint) -> Result<()> {
27669        // Python: return f"CONSTRAINT {this} {expressions}"
27670        self.write_keyword("CONSTRAINT");
27671        self.write_space();
27672        self.generate_expression(&e.this)?;
27673        if !e.expressions.is_empty() {
27674            self.write_space();
27675            for (i, expr) in e.expressions.iter().enumerate() {
27676                if i > 0 {
27677                    self.write_space();
27678                }
27679                self.generate_expression(expr)?;
27680            }
27681        }
27682        Ok(())
27683    }
27684
27685    fn generate_convert_timezone(&mut self, e: &ConvertTimezone) -> Result<()> {
27686        // CONVERT_TIMEZONE([source_tz,] target_tz, timestamp)
27687        self.write_keyword("CONVERT_TIMEZONE");
27688        self.write("(");
27689        let mut first = true;
27690        if let Some(source_tz) = &e.source_tz {
27691            self.generate_expression(source_tz)?;
27692            first = false;
27693        }
27694        if let Some(target_tz) = &e.target_tz {
27695            if !first {
27696                self.write(", ");
27697            }
27698            self.generate_expression(target_tz)?;
27699            first = false;
27700        }
27701        if let Some(timestamp) = &e.timestamp {
27702            if !first {
27703                self.write(", ");
27704            }
27705            self.generate_expression(timestamp)?;
27706        }
27707        self.write(")");
27708        Ok(())
27709    }
27710
27711    fn generate_convert_to_charset(&mut self, e: &ConvertToCharset) -> Result<()> {
27712        // CONVERT(this USING dest)
27713        self.write_keyword("CONVERT");
27714        self.write("(");
27715        self.generate_expression(&e.this)?;
27716        if let Some(dest) = &e.dest {
27717            self.write_space();
27718            self.write_keyword("USING");
27719            self.write_space();
27720            self.generate_expression(dest)?;
27721        }
27722        self.write(")");
27723        Ok(())
27724    }
27725
27726    fn generate_copy(&mut self, e: &CopyStmt) -> Result<()> {
27727        self.write_keyword("COPY");
27728        if e.is_into {
27729            self.write_space();
27730            self.write_keyword("INTO");
27731        }
27732        self.write_space();
27733
27734        // Generate target table or query (or stage for COPY INTO @stage)
27735        if let Expression::Literal(lit) = &e.this {
27736            if let Literal::String(s) = lit.as_ref() {
27737                if s.starts_with('@') {
27738                    self.write(s);
27739                } else {
27740                    self.generate_expression(&e.this)?;
27741                }
27742            }
27743        } else {
27744            self.generate_expression(&e.this)?;
27745        }
27746
27747        // FROM or TO based on kind
27748        if e.kind {
27749            // kind=true means FROM (loading into table)
27750            if self.config.pretty {
27751                self.write_newline();
27752            } else {
27753                self.write_space();
27754            }
27755            self.write_keyword("FROM");
27756            self.write_space();
27757        } else if !e.files.is_empty() {
27758            // kind=false means TO (exporting)
27759            if self.config.pretty {
27760                self.write_newline();
27761            } else {
27762                self.write_space();
27763            }
27764            self.write_keyword("TO");
27765            self.write_space();
27766        }
27767
27768        // Generate source/destination files
27769        for (i, file) in e.files.iter().enumerate() {
27770            if i > 0 {
27771                self.write_space();
27772            }
27773            // For stage references (strings starting with @), output without quotes
27774            if let Expression::Literal(lit) = file {
27775                if let Literal::String(s) = lit.as_ref() {
27776                    if s.starts_with('@') {
27777                        self.write(s);
27778                    } else {
27779                        self.generate_expression(file)?;
27780                    }
27781                }
27782            } else if let Expression::Identifier(id) = file {
27783                // Backtick-quoted file path (Databricks style: `s3://link`)
27784                if id.quoted {
27785                    self.write("`");
27786                    self.write(&id.name);
27787                    self.write("`");
27788                } else {
27789                    self.generate_expression(file)?;
27790                }
27791            } else {
27792                self.generate_expression(file)?;
27793            }
27794        }
27795
27796        // Generate credentials if present (Snowflake style - not wrapped in WITH)
27797        if !e.with_wrapped {
27798            if let Some(ref creds) = e.credentials {
27799                if let Some(ref storage) = creds.storage {
27800                    if self.config.pretty {
27801                        self.write_newline();
27802                    } else {
27803                        self.write_space();
27804                    }
27805                    self.write_keyword("STORAGE_INTEGRATION");
27806                    self.write(" = ");
27807                    self.write(storage);
27808                }
27809                if creds.credentials.is_empty() {
27810                    // Empty credentials: CREDENTIALS = ()
27811                    if self.config.pretty {
27812                        self.write_newline();
27813                    } else {
27814                        self.write_space();
27815                    }
27816                    self.write_keyword("CREDENTIALS");
27817                    self.write(" = ()");
27818                } else {
27819                    if self.config.pretty {
27820                        self.write_newline();
27821                    } else {
27822                        self.write_space();
27823                    }
27824                    self.write_keyword("CREDENTIALS");
27825                    // Check if this is Redshift-style (single value with empty key)
27826                    // vs Snowflake-style (multiple key=value pairs)
27827                    if creds.credentials.len() == 1 && creds.credentials[0].0.is_empty() {
27828                        // Redshift style: CREDENTIALS 'value'
27829                        self.write(" '");
27830                        self.write(&creds.credentials[0].1);
27831                        self.write("'");
27832                    } else {
27833                        // Snowflake style: CREDENTIALS = (KEY='value' ...)
27834                        self.write(" = (");
27835                        for (i, (k, v)) in creds.credentials.iter().enumerate() {
27836                            if i > 0 {
27837                                self.write_space();
27838                            }
27839                            self.write(k);
27840                            self.write("='");
27841                            self.write(v);
27842                            self.write("'");
27843                        }
27844                        self.write(")");
27845                    }
27846                }
27847                if let Some(ref encryption) = creds.encryption {
27848                    self.write_space();
27849                    self.write_keyword("ENCRYPTION");
27850                    self.write(" = ");
27851                    self.write(encryption);
27852                }
27853            }
27854        }
27855
27856        // Generate parameters
27857        if !e.params.is_empty() {
27858            if e.with_wrapped {
27859                // DuckDB/PostgreSQL/TSQL WITH (...) format
27860                self.write_space();
27861                self.write_keyword("WITH");
27862                self.write(" (");
27863                for (i, param) in e.params.iter().enumerate() {
27864                    if i > 0 {
27865                        self.write(", ");
27866                    }
27867                    self.generate_copy_param_with_format(param)?;
27868                }
27869                self.write(")");
27870            } else {
27871                // Snowflake/Redshift format: KEY = VALUE or KEY VALUE (space separated, no WITH wrapper)
27872                // For Redshift: IAM_ROLE value, CREDENTIALS 'value', REGION 'value', FORMAT type
27873                // For Snowflake: KEY = VALUE
27874                for param in &e.params {
27875                    if self.config.pretty {
27876                        self.write_newline();
27877                    } else {
27878                        self.write_space();
27879                    }
27880                    // Preserve original case of parameter name (important for Redshift COPY options)
27881                    self.write(&param.name);
27882                    if let Some(ref value) = param.value {
27883                        // Use = only if it was present in the original (param.eq)
27884                        if param.eq {
27885                            self.write(" = ");
27886                        } else {
27887                            self.write(" ");
27888                        }
27889                        if !param.values.is_empty() {
27890                            self.write("(");
27891                            for (i, v) in param.values.iter().enumerate() {
27892                                if i > 0 {
27893                                    self.write_space();
27894                                }
27895                                self.generate_copy_nested_param(v)?;
27896                            }
27897                            self.write(")");
27898                        } else {
27899                            // For COPY parameter values, output identifiers without quoting
27900                            self.generate_copy_param_value(value)?;
27901                        }
27902                    } else if !param.values.is_empty() {
27903                        // For varlen options like FORMAT_OPTIONS, COPY_OPTIONS - no = before (
27904                        if param.eq {
27905                            self.write(" = (");
27906                        } else {
27907                            self.write(" (");
27908                        }
27909                        // Determine separator for values inside parentheses:
27910                        // - Snowflake FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|') → space-separated (has = before parens)
27911                        // - Databricks FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') → comma-separated (no = before parens)
27912                        // - Simple value lists like FILES = ('file1', 'file2') → comma-separated
27913                        let is_key_value_pairs = param
27914                            .values
27915                            .first()
27916                            .map_or(false, |v| matches!(v, Expression::Eq(_)));
27917                        let sep = if is_key_value_pairs && param.eq {
27918                            " "
27919                        } else {
27920                            ", "
27921                        };
27922                        for (i, v) in param.values.iter().enumerate() {
27923                            if i > 0 {
27924                                self.write(sep);
27925                            }
27926                            self.generate_copy_nested_param(v)?;
27927                        }
27928                        self.write(")");
27929                    }
27930                }
27931            }
27932        }
27933
27934        Ok(())
27935    }
27936
27937    /// Generate a COPY parameter in WITH (...) format
27938    /// Handles both KEY = VALUE (TSQL) and KEY VALUE (DuckDB/PostgreSQL) formats
27939    fn generate_copy_param_with_format(&mut self, param: &CopyParameter) -> Result<()> {
27940        self.write_keyword(&param.name);
27941        if !param.values.is_empty() {
27942            // Nested values: CREDENTIAL = (IDENTITY='...', SECRET='...')
27943            self.write(" = (");
27944            for (i, v) in param.values.iter().enumerate() {
27945                if i > 0 {
27946                    self.write(", ");
27947                }
27948                self.generate_copy_nested_param(v)?;
27949            }
27950            self.write(")");
27951        } else if let Some(ref value) = param.value {
27952            if param.eq {
27953                self.write(" = ");
27954            } else {
27955                self.write(" ");
27956            }
27957            self.generate_expression(value)?;
27958        }
27959        Ok(())
27960    }
27961
27962    /// Generate nested parameter for COPY statements (KEY=VALUE without spaces)
27963    fn generate_copy_nested_param(&mut self, expr: &Expression) -> Result<()> {
27964        match expr {
27965            Expression::Eq(eq) => {
27966                // Generate key
27967                match &eq.left {
27968                    Expression::Column(c) => self.write(&c.name.name),
27969                    _ => self.generate_expression(&eq.left)?,
27970                }
27971                self.write("=");
27972                // Generate value
27973                match &eq.right {
27974                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
27975                        let Literal::String(s) = lit.as_ref() else {
27976                            unreachable!()
27977                        };
27978                        self.write("'");
27979                        self.write(s);
27980                        self.write("'");
27981                    }
27982                    Expression::Tuple(t) => {
27983                        // For lists like NULL_IF=('', 'str1')
27984                        self.write("(");
27985                        if self.config.pretty {
27986                            self.write_newline();
27987                            self.indent_level += 1;
27988                            for (i, item) in t.expressions.iter().enumerate() {
27989                                if i > 0 {
27990                                    self.write(", ");
27991                                }
27992                                self.write_indent();
27993                                self.generate_expression(item)?;
27994                            }
27995                            self.write_newline();
27996                            self.indent_level -= 1;
27997                        } else {
27998                            for (i, item) in t.expressions.iter().enumerate() {
27999                                if i > 0 {
28000                                    self.write(", ");
28001                                }
28002                                self.generate_expression(item)?;
28003                            }
28004                        }
28005                        self.write(")");
28006                    }
28007                    _ => self.generate_expression(&eq.right)?,
28008                }
28009                Ok(())
28010            }
28011            Expression::Column(c) => {
28012                // Standalone keyword like COMPRESSION
28013                self.write(&c.name.name);
28014                Ok(())
28015            }
28016            _ => self.generate_expression(expr),
28017        }
28018    }
28019
28020    /// Generate a COPY parameter value, outputting identifiers/columns without quoting
28021    /// This is needed for Redshift-style COPY params like: IAM_ROLE default, FORMAT orc
28022    fn generate_copy_param_value(&mut self, expr: &Expression) -> Result<()> {
28023        match expr {
28024            Expression::Column(c) => {
28025                // Output identifier, preserving quotes if originally quoted
28026                if c.name.quoted {
28027                    self.write("\"");
28028                    self.write(&c.name.name);
28029                    self.write("\"");
28030                } else {
28031                    self.write(&c.name.name);
28032                }
28033                Ok(())
28034            }
28035            Expression::Identifier(id) => {
28036                // Output identifier, preserving quotes if originally quoted
28037                if id.quoted {
28038                    self.write("\"");
28039                    self.write(&id.name);
28040                    self.write("\"");
28041                } else {
28042                    self.write(&id.name);
28043                }
28044                Ok(())
28045            }
28046            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
28047                let Literal::String(s) = lit.as_ref() else {
28048                    unreachable!()
28049                };
28050                // Output string with quotes
28051                self.write("'");
28052                self.write(s);
28053                self.write("'");
28054                Ok(())
28055            }
28056            _ => self.generate_expression(expr),
28057        }
28058    }
28059
28060    fn generate_copy_parameter(&mut self, e: &CopyParameter) -> Result<()> {
28061        self.write_keyword(&e.name);
28062        if let Some(ref value) = e.value {
28063            if e.eq {
28064                self.write(" = ");
28065            } else {
28066                self.write(" ");
28067            }
28068            self.generate_expression(value)?;
28069        }
28070        if !e.values.is_empty() {
28071            if e.eq {
28072                self.write(" = ");
28073            } else {
28074                self.write(" ");
28075            }
28076            self.write("(");
28077            for (i, v) in e.values.iter().enumerate() {
28078                if i > 0 {
28079                    self.write(", ");
28080                }
28081                self.generate_expression(v)?;
28082            }
28083            self.write(")");
28084        }
28085        Ok(())
28086    }
28087
28088    fn generate_corr(&mut self, e: &Corr) -> Result<()> {
28089        // CORR(this, expression)
28090        self.write_keyword("CORR");
28091        self.write("(");
28092        self.generate_expression(&e.this)?;
28093        self.write(", ");
28094        self.generate_expression(&e.expression)?;
28095        self.write(")");
28096        Ok(())
28097    }
28098
28099    fn generate_cosine_distance(&mut self, e: &CosineDistance) -> Result<()> {
28100        // COSINE_DISTANCE(this, expression)
28101        self.write_keyword("COSINE_DISTANCE");
28102        self.write("(");
28103        self.generate_expression(&e.this)?;
28104        self.write(", ");
28105        self.generate_expression(&e.expression)?;
28106        self.write(")");
28107        Ok(())
28108    }
28109
28110    fn generate_covar_pop(&mut self, e: &CovarPop) -> Result<()> {
28111        // COVAR_POP(this, expression)
28112        self.write_keyword("COVAR_POP");
28113        self.write("(");
28114        self.generate_expression(&e.this)?;
28115        self.write(", ");
28116        self.generate_expression(&e.expression)?;
28117        self.write(")");
28118        Ok(())
28119    }
28120
28121    fn generate_covar_samp(&mut self, e: &CovarSamp) -> Result<()> {
28122        // COVAR_SAMP(this, expression)
28123        self.write_keyword("COVAR_SAMP");
28124        self.write("(");
28125        self.generate_expression(&e.this)?;
28126        self.write(", ");
28127        self.generate_expression(&e.expression)?;
28128        self.write(")");
28129        Ok(())
28130    }
28131
28132    fn generate_credentials(&mut self, e: &Credentials) -> Result<()> {
28133        // CREDENTIALS (key1='value1', key2='value2')
28134        self.write_keyword("CREDENTIALS");
28135        self.write(" (");
28136        for (i, (key, value)) in e.credentials.iter().enumerate() {
28137            if i > 0 {
28138                self.write(", ");
28139            }
28140            self.write(key);
28141            self.write("='");
28142            self.write(value);
28143            self.write("'");
28144        }
28145        self.write(")");
28146        Ok(())
28147    }
28148
28149    fn generate_credentials_property(&mut self, e: &CredentialsProperty) -> Result<()> {
28150        // CREDENTIALS=(expressions)
28151        self.write_keyword("CREDENTIALS");
28152        self.write("=(");
28153        for (i, expr) in e.expressions.iter().enumerate() {
28154            if i > 0 {
28155                self.write(", ");
28156            }
28157            self.generate_expression(expr)?;
28158        }
28159        self.write(")");
28160        Ok(())
28161    }
28162
28163    fn generate_cte(&mut self, e: &Cte) -> Result<()> {
28164        use crate::dialects::DialectType;
28165
28166        // Python: return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
28167        // Output: alias [(col1, col2, ...)] AS [MATERIALIZED|NOT MATERIALIZED] (subquery)
28168        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) && !e.alias_first {
28169            self.generate_expression(&e.this)?;
28170            self.write_space();
28171            self.write_keyword("AS");
28172            self.write_space();
28173            self.generate_identifier(&e.alias)?;
28174            return Ok(());
28175        }
28176        self.write(&e.alias.name);
28177
28178        // BigQuery doesn't support column aliases in CTE definitions
28179        let skip_cte_columns = matches!(self.config.dialect, Some(DialectType::BigQuery));
28180
28181        if !e.columns.is_empty() && !skip_cte_columns {
28182            self.write("(");
28183            for (i, col) in e.columns.iter().enumerate() {
28184                if i > 0 {
28185                    self.write(", ");
28186                }
28187                self.write(&col.name);
28188            }
28189            self.write(")");
28190        }
28191        // USING KEY (columns) for DuckDB recursive CTEs
28192        if !e.key_expressions.is_empty() {
28193            self.write_space();
28194            self.write_keyword("USING KEY");
28195            self.write(" (");
28196            for (i, key) in e.key_expressions.iter().enumerate() {
28197                if i > 0 {
28198                    self.write(", ");
28199                }
28200                self.write(&key.name);
28201            }
28202            self.write(")");
28203        }
28204        self.write_space();
28205        self.write_keyword("AS");
28206        self.write_space();
28207        if let Some(materialized) = e.materialized {
28208            if materialized {
28209                self.write_keyword("MATERIALIZED");
28210            } else {
28211                self.write_keyword("NOT MATERIALIZED");
28212            }
28213            self.write_space();
28214        }
28215        self.write("(");
28216        self.generate_expression(&e.this)?;
28217        self.write(")");
28218        Ok(())
28219    }
28220
28221    fn generate_cube(&mut self, e: &Cube) -> Result<()> {
28222        // Python: return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE"
28223        if e.expressions.is_empty() {
28224            self.write_keyword("WITH CUBE");
28225        } else {
28226            self.write_keyword("CUBE");
28227            self.write("(");
28228            for (i, expr) in e.expressions.iter().enumerate() {
28229                if i > 0 {
28230                    self.write(", ");
28231                }
28232                self.generate_expression(expr)?;
28233            }
28234            self.write(")");
28235        }
28236        Ok(())
28237    }
28238
28239    fn generate_current_datetime(&mut self, e: &CurrentDatetime) -> Result<()> {
28240        // CURRENT_DATETIME or CURRENT_DATETIME(timezone)
28241        self.write_keyword("CURRENT_DATETIME");
28242        if let Some(this) = &e.this {
28243            self.write("(");
28244            self.generate_expression(this)?;
28245            self.write(")");
28246        }
28247        Ok(())
28248    }
28249
28250    fn generate_current_schema(&mut self, _e: &CurrentSchema) -> Result<()> {
28251        // CURRENT_SCHEMA - no arguments
28252        self.write_keyword("CURRENT_SCHEMA");
28253        Ok(())
28254    }
28255
28256    fn generate_current_schemas(&mut self, e: &CurrentSchemas) -> Result<()> {
28257        // CURRENT_SCHEMAS(include_implicit)
28258        self.write_keyword("CURRENT_SCHEMAS");
28259        self.write("(");
28260        // Snowflake: drop the argument (CURRENT_SCHEMAS() takes no args)
28261        if !matches!(
28262            self.config.dialect,
28263            Some(crate::dialects::DialectType::Snowflake)
28264        ) {
28265            if let Some(this) = &e.this {
28266                self.generate_expression(this)?;
28267            }
28268        }
28269        self.write(")");
28270        Ok(())
28271    }
28272
28273    fn generate_current_user(&mut self, e: &CurrentUser) -> Result<()> {
28274        // CURRENT_USER or CURRENT_USER()
28275        self.write_keyword("CURRENT_USER");
28276        // Some dialects always need parens: Snowflake, Spark, Hive, DuckDB, BigQuery, MySQL, Databricks
28277        let needs_parens = e.this.is_some()
28278            || matches!(
28279                self.config.dialect,
28280                Some(DialectType::Snowflake)
28281                    | Some(DialectType::Spark)
28282                    | Some(DialectType::Hive)
28283                    | Some(DialectType::DuckDB)
28284                    | Some(DialectType::BigQuery)
28285                    | Some(DialectType::MySQL)
28286                    | Some(DialectType::Databricks)
28287            );
28288        if needs_parens {
28289            self.write("()");
28290        }
28291        Ok(())
28292    }
28293
28294    fn generate_d_pipe(&mut self, e: &DPipe) -> Result<()> {
28295        // In Solr, || is OR, not string concatenation (DPIPE_IS_STRING_CONCAT = False)
28296        if self.config.dialect == Some(DialectType::Solr) {
28297            self.generate_expression(&e.this)?;
28298            self.write(" ");
28299            self.write_keyword("OR");
28300            self.write(" ");
28301            self.generate_expression(&e.expression)?;
28302        } else if self.config.dialect == Some(DialectType::MySQL) {
28303            self.generate_mysql_concat_from_dpipe(e)?;
28304        } else {
28305            // String concatenation: this || expression
28306            self.generate_expression(&e.this)?;
28307            self.write(" || ");
28308            self.generate_expression(&e.expression)?;
28309        }
28310        Ok(())
28311    }
28312
28313    fn generate_data_blocksize_property(&mut self, e: &DataBlocksizeProperty) -> Result<()> {
28314        // DATABLOCKSIZE=... (Teradata)
28315        self.write_keyword("DATABLOCKSIZE");
28316        self.write("=");
28317        if let Some(size) = e.size {
28318            self.write(&size.to_string());
28319            if let Some(units) = &e.units {
28320                self.write_space();
28321                self.generate_expression(units)?;
28322            }
28323        } else if e.minimum.is_some() {
28324            self.write_keyword("MINIMUM");
28325        } else if e.maximum.is_some() {
28326            self.write_keyword("MAXIMUM");
28327        } else if e.default.is_some() {
28328            self.write_keyword("DEFAULT");
28329        }
28330        Ok(())
28331    }
28332
28333    fn generate_data_deletion_property(&mut self, e: &DataDeletionProperty) -> Result<()> {
28334        // DATA_DELETION=ON or DATA_DELETION=OFF or DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=...)
28335        self.write_keyword("DATA_DELETION");
28336        self.write("=");
28337
28338        let is_on = matches!(&*e.on, Expression::Boolean(BooleanLiteral { value: true }));
28339        let has_options = e.filter_column.is_some() || e.retention_period.is_some();
28340
28341        if is_on {
28342            self.write_keyword("ON");
28343            if has_options {
28344                self.write("(");
28345                let mut first = true;
28346                if let Some(filter_column) = &e.filter_column {
28347                    self.write_keyword("FILTER_COLUMN");
28348                    self.write("=");
28349                    self.generate_expression(filter_column)?;
28350                    first = false;
28351                }
28352                if let Some(retention_period) = &e.retention_period {
28353                    if !first {
28354                        self.write(", ");
28355                    }
28356                    self.write_keyword("RETENTION_PERIOD");
28357                    self.write("=");
28358                    self.generate_expression(retention_period)?;
28359                }
28360                self.write(")");
28361            }
28362        } else {
28363            self.write_keyword("OFF");
28364        }
28365        Ok(())
28366    }
28367
28368    /// Generate a Date function expression
28369    /// For Exasol: {d'value'} -> TO_DATE('value')
28370    /// For other dialects: DATE('value')
28371    fn generate_date_func(&mut self, e: &UnaryFunc) -> Result<()> {
28372        use crate::dialects::DialectType;
28373        use crate::expressions::Literal;
28374
28375        match self.config.dialect {
28376            // Exasol uses TO_DATE for Date expressions
28377            Some(DialectType::Exasol) => {
28378                self.write_keyword("TO_DATE");
28379                self.write("(");
28380                // Extract the string value from the expression if it's a string literal
28381                match &e.this {
28382                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
28383                        let Literal::String(s) = lit.as_ref() else {
28384                            unreachable!()
28385                        };
28386                        self.write("'");
28387                        self.write(s);
28388                        self.write("'");
28389                    }
28390                    _ => {
28391                        self.generate_expression(&e.this)?;
28392                    }
28393                }
28394                self.write(")");
28395            }
28396            // Standard: DATE(value)
28397            _ => {
28398                self.write_keyword("DATE");
28399                self.write("(");
28400                self.generate_expression(&e.this)?;
28401                self.write(")");
28402            }
28403        }
28404        Ok(())
28405    }
28406
28407    fn generate_date_bin(&mut self, e: &DateBin) -> Result<()> {
28408        // DATE_BIN(interval, timestamp[, origin])
28409        self.write_keyword("DATE_BIN");
28410        self.write("(");
28411        self.generate_expression(&e.this)?;
28412        self.write(", ");
28413        self.generate_expression(&e.expression)?;
28414        if let Some(origin) = &e.origin {
28415            self.write(", ");
28416            self.generate_expression(origin)?;
28417        }
28418        self.write(")");
28419        Ok(())
28420    }
28421
28422    fn generate_date_format_column_constraint(
28423        &mut self,
28424        e: &DateFormatColumnConstraint,
28425    ) -> Result<()> {
28426        // FORMAT 'format_string' (Teradata)
28427        self.write_keyword("FORMAT");
28428        self.write_space();
28429        self.generate_expression(&e.this)?;
28430        Ok(())
28431    }
28432
28433    fn generate_date_from_parts(&mut self, e: &DateFromParts) -> Result<()> {
28434        // DATE_FROM_PARTS(year, month, day) or DATEFROMPARTS(year, month, day)
28435        self.write_keyword("DATE_FROM_PARTS");
28436        self.write("(");
28437        let mut first = true;
28438        if let Some(year) = &e.year {
28439            self.generate_expression(year)?;
28440            first = false;
28441        }
28442        if let Some(month) = &e.month {
28443            if !first {
28444                self.write(", ");
28445            }
28446            self.generate_expression(month)?;
28447            first = false;
28448        }
28449        if let Some(day) = &e.day {
28450            if !first {
28451                self.write(", ");
28452            }
28453            self.generate_expression(day)?;
28454        }
28455        self.write(")");
28456        Ok(())
28457    }
28458
28459    fn generate_datetime(&mut self, e: &Datetime) -> Result<()> {
28460        // DATETIME(this) or DATETIME(this, expression)
28461        self.write_keyword("DATETIME");
28462        self.write("(");
28463        self.generate_expression(&e.this)?;
28464        if let Some(expr) = &e.expression {
28465            self.write(", ");
28466            self.generate_expression(expr)?;
28467        }
28468        self.write(")");
28469        Ok(())
28470    }
28471
28472    fn generate_datetime_add(&mut self, e: &DatetimeAdd) -> Result<()> {
28473        // DATETIME_ADD(this, expression, unit)
28474        self.write_keyword("DATETIME_ADD");
28475        self.write("(");
28476        self.generate_expression(&e.this)?;
28477        self.write(", ");
28478        self.generate_expression(&e.expression)?;
28479        if let Some(unit) = &e.unit {
28480            self.write(", ");
28481            self.write_keyword(unit);
28482        }
28483        self.write(")");
28484        Ok(())
28485    }
28486
28487    fn generate_datetime_diff(&mut self, e: &DatetimeDiff) -> Result<()> {
28488        // DATETIME_DIFF(this, expression, unit)
28489        self.write_keyword("DATETIME_DIFF");
28490        self.write("(");
28491        self.generate_expression(&e.this)?;
28492        self.write(", ");
28493        self.generate_expression(&e.expression)?;
28494        if let Some(unit) = &e.unit {
28495            self.write(", ");
28496            self.write_keyword(unit);
28497        }
28498        self.write(")");
28499        Ok(())
28500    }
28501
28502    fn generate_datetime_sub(&mut self, e: &DatetimeSub) -> Result<()> {
28503        // DATETIME_SUB(this, expression, unit)
28504        self.write_keyword("DATETIME_SUB");
28505        self.write("(");
28506        self.generate_expression(&e.this)?;
28507        self.write(", ");
28508        self.generate_expression(&e.expression)?;
28509        if let Some(unit) = &e.unit {
28510            self.write(", ");
28511            self.write_keyword(unit);
28512        }
28513        self.write(")");
28514        Ok(())
28515    }
28516
28517    fn generate_datetime_trunc(&mut self, e: &DatetimeTrunc) -> Result<()> {
28518        // DATETIME_TRUNC(this, unit, zone)
28519        self.write_keyword("DATETIME_TRUNC");
28520        self.write("(");
28521        self.generate_expression(&e.this)?;
28522        self.write(", ");
28523        self.write_keyword(&e.unit);
28524        if let Some(zone) = &e.zone {
28525            self.write(", ");
28526            self.generate_expression(zone)?;
28527        }
28528        self.write(")");
28529        Ok(())
28530    }
28531
28532    fn generate_dayname(&mut self, e: &Dayname) -> Result<()> {
28533        // DAYNAME(this)
28534        self.write_keyword("DAYNAME");
28535        self.write("(");
28536        self.generate_expression(&e.this)?;
28537        self.write(")");
28538        Ok(())
28539    }
28540
28541    fn generate_declare(&mut self, e: &Declare) -> Result<()> {
28542        // DECLARE [OR REPLACE] var1 AS type1, var2 AS type2, ...
28543        self.write_keyword("DECLARE");
28544        self.write_space();
28545        if e.replace {
28546            self.write_keyword("OR");
28547            self.write_space();
28548            self.write_keyword("REPLACE");
28549            self.write_space();
28550        }
28551        for (i, expr) in e.expressions.iter().enumerate() {
28552            if i > 0 {
28553                self.write(", ");
28554            }
28555            self.generate_expression(expr)?;
28556        }
28557        Ok(())
28558    }
28559
28560    fn generate_declare_item(&mut self, e: &DeclareItem) -> Result<()> {
28561        use crate::dialects::DialectType;
28562
28563        // variable TYPE [DEFAULT default]
28564        self.generate_expression(&e.this)?;
28565        // BigQuery multi-variable: DECLARE X, Y, Z INT64
28566        for name in &e.additional_names {
28567            self.write(", ");
28568            self.generate_expression(name)?;
28569        }
28570        if let Some(kind) = &e.kind {
28571            self.write_space();
28572            // BigQuery uses: DECLARE x INT64 DEFAULT value (no AS)
28573            // TSQL: Always includes AS (normalization)
28574            // Others: Include AS if present in original
28575            match self.config.dialect {
28576                Some(DialectType::BigQuery) => {
28577                    self.write(kind);
28578                }
28579                Some(DialectType::TSQL) => {
28580                    // TSQL DECLARE: no AS keyword (sqlglot convention)
28581                    // Normalize INT to INTEGER for simple declarations
28582                    // Complex TABLE declarations (with CLUSTERED/INDEX) are preserved as-is
28583                    let is_complex_table = kind.starts_with("TABLE")
28584                        && (kind.contains("CLUSTERED") || kind.contains("INDEX"));
28585                    if is_complex_table {
28586                        self.write(kind);
28587                    } else if kind == "INT" {
28588                        self.write("INTEGER");
28589                    } else if kind.starts_with("TABLE") {
28590                        // Normalize INT to INTEGER inside simple TABLE column definitions
28591                        let normalized = kind
28592                            .replace(" INT ", " INTEGER ")
28593                            .replace(" INT,", " INTEGER,")
28594                            .replace(" INT)", " INTEGER)")
28595                            .replace("(INT ", "(INTEGER ");
28596                        self.write(&normalized);
28597                    } else {
28598                        self.write(kind);
28599                    }
28600                }
28601                _ => {
28602                    if e.has_as {
28603                        self.write_keyword("AS");
28604                        self.write_space();
28605                    }
28606                    self.write(kind);
28607                }
28608            }
28609        }
28610        if let Some(default) = &e.default {
28611            // BigQuery uses DEFAULT, others use =
28612            match self.config.dialect {
28613                Some(DialectType::BigQuery) => {
28614                    self.write_space();
28615                    self.write_keyword("DEFAULT");
28616                    self.write_space();
28617                }
28618                _ => {
28619                    self.write(" = ");
28620                }
28621            }
28622            self.generate_expression(default)?;
28623        }
28624        Ok(())
28625    }
28626
28627    fn generate_decode_case(&mut self, e: &DecodeCase) -> Result<()> {
28628        // DECODE(expr, search1, result1, search2, result2, ..., default)
28629        self.write_keyword("DECODE");
28630        self.write("(");
28631        for (i, expr) in e.expressions.iter().enumerate() {
28632            if i > 0 {
28633                self.write(", ");
28634            }
28635            self.generate_expression(expr)?;
28636        }
28637        self.write(")");
28638        Ok(())
28639    }
28640
28641    fn generate_decompress_binary(&mut self, e: &DecompressBinary) -> Result<()> {
28642        // DECOMPRESS(expr, 'method')
28643        self.write_keyword("DECOMPRESS");
28644        self.write("(");
28645        self.generate_expression(&e.this)?;
28646        self.write(", '");
28647        self.write(&e.method);
28648        self.write("')");
28649        Ok(())
28650    }
28651
28652    fn generate_decompress_string(&mut self, e: &DecompressString) -> Result<()> {
28653        // DECOMPRESS(expr, 'method')
28654        self.write_keyword("DECOMPRESS");
28655        self.write("(");
28656        self.generate_expression(&e.this)?;
28657        self.write(", '");
28658        self.write(&e.method);
28659        self.write("')");
28660        Ok(())
28661    }
28662
28663    fn generate_decrypt(&mut self, e: &Decrypt) -> Result<()> {
28664        // DECRYPT(value, passphrase [, aad [, algorithm]])
28665        self.write_keyword("DECRYPT");
28666        self.write("(");
28667        self.generate_expression(&e.this)?;
28668        if let Some(passphrase) = &e.passphrase {
28669            self.write(", ");
28670            self.generate_expression(passphrase)?;
28671        }
28672        if let Some(aad) = &e.aad {
28673            self.write(", ");
28674            self.generate_expression(aad)?;
28675        }
28676        if let Some(method) = &e.encryption_method {
28677            self.write(", ");
28678            self.generate_expression(method)?;
28679        }
28680        self.write(")");
28681        Ok(())
28682    }
28683
28684    fn generate_decrypt_raw(&mut self, e: &DecryptRaw) -> Result<()> {
28685        // DECRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
28686        self.write_keyword("DECRYPT_RAW");
28687        self.write("(");
28688        self.generate_expression(&e.this)?;
28689        if let Some(key) = &e.key {
28690            self.write(", ");
28691            self.generate_expression(key)?;
28692        }
28693        if let Some(iv) = &e.iv {
28694            self.write(", ");
28695            self.generate_expression(iv)?;
28696        }
28697        if let Some(aad) = &e.aad {
28698            self.write(", ");
28699            self.generate_expression(aad)?;
28700        }
28701        if let Some(method) = &e.encryption_method {
28702            self.write(", ");
28703            self.generate_expression(method)?;
28704        }
28705        self.write(")");
28706        Ok(())
28707    }
28708
28709    fn generate_definer_property(&mut self, e: &DefinerProperty) -> Result<()> {
28710        // DEFINER = user
28711        self.write_keyword("DEFINER");
28712        self.write(" = ");
28713        self.generate_expression(&e.this)?;
28714        Ok(())
28715    }
28716
28717    fn generate_detach(&mut self, e: &Detach) -> Result<()> {
28718        // Python: DETACH[DATABASE IF EXISTS] this
28719        self.write_keyword("DETACH");
28720        if e.exists {
28721            self.write_keyword(" DATABASE IF EXISTS");
28722        }
28723        self.write_space();
28724        self.generate_expression(&e.this)?;
28725        Ok(())
28726    }
28727
28728    fn generate_dict_property(&mut self, e: &DictProperty) -> Result<()> {
28729        let property_name = match e.this.as_ref() {
28730            Expression::Identifier(id) => id.name.as_str(),
28731            Expression::Var(v) => v.this.as_str(),
28732            _ => "DICTIONARY",
28733        };
28734        self.write_keyword(property_name);
28735        self.write("(");
28736        self.write(&e.kind);
28737        if let Some(settings) = &e.settings {
28738            self.write("(");
28739            if let Expression::Tuple(t) = settings.as_ref() {
28740                if self.config.pretty && !t.expressions.is_empty() {
28741                    self.write_newline();
28742                    self.indent_level += 1;
28743                    for (i, pair) in t.expressions.iter().enumerate() {
28744                        if i > 0 {
28745                            self.write(",");
28746                            self.write_newline();
28747                        }
28748                        self.write_indent();
28749                        if let Expression::Tuple(pair_tuple) = pair {
28750                            if let Some(k) = pair_tuple.expressions.first() {
28751                                self.generate_expression(k)?;
28752                            }
28753                            if let Some(v) = pair_tuple.expressions.get(1) {
28754                                self.write(" ");
28755                                self.generate_expression(v)?;
28756                            }
28757                        } else {
28758                            self.generate_expression(pair)?;
28759                        }
28760                    }
28761                    self.indent_level -= 1;
28762                    self.write_newline();
28763                    self.write_indent();
28764                } else {
28765                    for (i, pair) in t.expressions.iter().enumerate() {
28766                        if i > 0 {
28767                            // ClickHouse dict properties are space-separated, not comma-separated
28768                            self.write(" ");
28769                        }
28770                        if let Expression::Tuple(pair_tuple) = pair {
28771                            if let Some(k) = pair_tuple.expressions.first() {
28772                                self.generate_expression(k)?;
28773                            }
28774                            if let Some(v) = pair_tuple.expressions.get(1) {
28775                                self.write(" ");
28776                                self.generate_expression(v)?;
28777                            }
28778                        } else {
28779                            self.generate_expression(pair)?;
28780                        }
28781                    }
28782                }
28783            } else {
28784                self.generate_expression(settings)?;
28785            }
28786            self.write(")");
28787        } else {
28788            // No settings but kind had parens (e.g., SOURCE(NULL()), LAYOUT(FLAT()))
28789            self.write("()");
28790        }
28791        self.write(")");
28792        Ok(())
28793    }
28794
28795    fn generate_dict_range(&mut self, e: &DictRange) -> Result<()> {
28796        let property_name = match e.this.as_ref() {
28797            Expression::Identifier(id) => id.name.as_str(),
28798            Expression::Var(v) => v.this.as_str(),
28799            _ => "RANGE",
28800        };
28801        self.write_keyword(property_name);
28802        self.write("(");
28803        if let Some(min) = &e.min {
28804            self.write_keyword("MIN");
28805            self.write_space();
28806            self.generate_expression(min)?;
28807        }
28808        if let Some(max) = &e.max {
28809            self.write_space();
28810            self.write_keyword("MAX");
28811            self.write_space();
28812            self.generate_expression(max)?;
28813        }
28814        self.write(")");
28815        Ok(())
28816    }
28817
28818    fn generate_directory(&mut self, e: &Directory) -> Result<()> {
28819        // Python: {local}DIRECTORY {this}{row_format}
28820        if e.local.is_some() {
28821            self.write_keyword("LOCAL ");
28822        }
28823        self.write_keyword("DIRECTORY");
28824        self.write_space();
28825        self.generate_expression(&e.this)?;
28826        if let Some(row_format) = &e.row_format {
28827            self.write_space();
28828            self.generate_expression(row_format)?;
28829        }
28830        Ok(())
28831    }
28832
28833    fn generate_dist_key_property(&mut self, e: &DistKeyProperty) -> Result<()> {
28834        // Redshift: DISTKEY(column)
28835        self.write_keyword("DISTKEY");
28836        self.write("(");
28837        self.generate_expression(&e.this)?;
28838        self.write(")");
28839        Ok(())
28840    }
28841
28842    fn generate_dist_style_property(&mut self, e: &DistStyleProperty) -> Result<()> {
28843        // Redshift: DISTSTYLE KEY|ALL|EVEN|AUTO
28844        self.write_keyword("DISTSTYLE");
28845        self.write_space();
28846        self.generate_expression(&e.this)?;
28847        Ok(())
28848    }
28849
28850    fn generate_distribute_by(&mut self, e: &DistributeBy) -> Result<()> {
28851        // Python: "DISTRIBUTE BY" expressions
28852        self.write_keyword("DISTRIBUTE BY");
28853        self.write_space();
28854        for (i, expr) in e.expressions.iter().enumerate() {
28855            if i > 0 {
28856                self.write(", ");
28857            }
28858            self.generate_expression(expr)?;
28859        }
28860        Ok(())
28861    }
28862
28863    fn generate_distributed_by_property(&mut self, e: &DistributedByProperty) -> Result<()> {
28864        // Python: DISTRIBUTED BY kind (expressions) BUCKETS buckets order
28865        self.write_keyword("DISTRIBUTED BY");
28866        self.write_space();
28867        self.write(&e.kind);
28868        if !e.expressions.is_empty() {
28869            self.write(" (");
28870            for (i, expr) in e.expressions.iter().enumerate() {
28871                if i > 0 {
28872                    self.write(", ");
28873                }
28874                self.generate_expression(expr)?;
28875            }
28876            self.write(")");
28877        }
28878        if let Some(buckets) = &e.buckets {
28879            self.write_space();
28880            self.write_keyword("BUCKETS");
28881            self.write_space();
28882            self.generate_expression(buckets)?;
28883        }
28884        if let Some(order) = &e.order {
28885            self.write_space();
28886            self.generate_expression(order)?;
28887        }
28888        Ok(())
28889    }
28890
28891    fn generate_dot_product(&mut self, e: &DotProduct) -> Result<()> {
28892        // DOT_PRODUCT(vector1, vector2)
28893        self.write_keyword("DOT_PRODUCT");
28894        self.write("(");
28895        self.generate_expression(&e.this)?;
28896        self.write(", ");
28897        self.generate_expression(&e.expression)?;
28898        self.write(")");
28899        Ok(())
28900    }
28901
28902    fn generate_drop_partition(&mut self, e: &DropPartition) -> Result<()> {
28903        // Python: DROP{IF EXISTS }expressions
28904        self.write_keyword("DROP");
28905        if e.exists {
28906            self.write_keyword(" IF EXISTS ");
28907        } else {
28908            self.write_space();
28909        }
28910        for (i, expr) in e.expressions.iter().enumerate() {
28911            if i > 0 {
28912                self.write(", ");
28913            }
28914            self.generate_expression(expr)?;
28915        }
28916        Ok(())
28917    }
28918
28919    fn generate_duplicate_key_property(&mut self, e: &DuplicateKeyProperty) -> Result<()> {
28920        // Python: DUPLICATE KEY (expressions)
28921        self.write_keyword("DUPLICATE KEY");
28922        self.write(" (");
28923        for (i, expr) in e.expressions.iter().enumerate() {
28924            if i > 0 {
28925                self.write(", ");
28926            }
28927            self.generate_expression(expr)?;
28928        }
28929        self.write(")");
28930        Ok(())
28931    }
28932
28933    fn generate_elt(&mut self, e: &Elt) -> Result<()> {
28934        // ELT(index, str1, str2, ...)
28935        self.write_keyword("ELT");
28936        self.write("(");
28937        self.generate_expression(&e.this)?;
28938        for expr in &e.expressions {
28939            self.write(", ");
28940            self.generate_expression(expr)?;
28941        }
28942        self.write(")");
28943        Ok(())
28944    }
28945
28946    fn generate_encode(&mut self, e: &Encode) -> Result<()> {
28947        // ENCODE(string, charset)
28948        self.write_keyword("ENCODE");
28949        self.write("(");
28950        self.generate_expression(&e.this)?;
28951        if let Some(charset) = &e.charset {
28952            self.write(", ");
28953            self.generate_expression(charset)?;
28954        }
28955        self.write(")");
28956        Ok(())
28957    }
28958
28959    fn generate_encode_property(&mut self, e: &EncodeProperty) -> Result<()> {
28960        // Python: [KEY ]ENCODE this [properties]
28961        if e.key.is_some() {
28962            self.write_keyword("KEY ");
28963        }
28964        self.write_keyword("ENCODE");
28965        self.write_space();
28966        self.generate_expression(&e.this)?;
28967        if !e.properties.is_empty() {
28968            self.write(" (");
28969            for (i, prop) in e.properties.iter().enumerate() {
28970                if i > 0 {
28971                    self.write(", ");
28972                }
28973                self.generate_expression(prop)?;
28974            }
28975            self.write(")");
28976        }
28977        Ok(())
28978    }
28979
28980    fn generate_encrypt(&mut self, e: &Encrypt) -> Result<()> {
28981        // ENCRYPT(value, passphrase [, aad [, algorithm]])
28982        self.write_keyword("ENCRYPT");
28983        self.write("(");
28984        self.generate_expression(&e.this)?;
28985        if let Some(passphrase) = &e.passphrase {
28986            self.write(", ");
28987            self.generate_expression(passphrase)?;
28988        }
28989        if let Some(aad) = &e.aad {
28990            self.write(", ");
28991            self.generate_expression(aad)?;
28992        }
28993        if let Some(method) = &e.encryption_method {
28994            self.write(", ");
28995            self.generate_expression(method)?;
28996        }
28997        self.write(")");
28998        Ok(())
28999    }
29000
29001    fn generate_encrypt_raw(&mut self, e: &EncryptRaw) -> Result<()> {
29002        // ENCRYPT_RAW(value, key [, iv [, aad [, algorithm]]])
29003        self.write_keyword("ENCRYPT_RAW");
29004        self.write("(");
29005        self.generate_expression(&e.this)?;
29006        if let Some(key) = &e.key {
29007            self.write(", ");
29008            self.generate_expression(key)?;
29009        }
29010        if let Some(iv) = &e.iv {
29011            self.write(", ");
29012            self.generate_expression(iv)?;
29013        }
29014        if let Some(aad) = &e.aad {
29015            self.write(", ");
29016            self.generate_expression(aad)?;
29017        }
29018        if let Some(method) = &e.encryption_method {
29019            self.write(", ");
29020            self.generate_expression(method)?;
29021        }
29022        self.write(")");
29023        Ok(())
29024    }
29025
29026    fn generate_engine_property(&mut self, e: &EngineProperty) -> Result<()> {
29027        // MySQL: ENGINE = InnoDB
29028        self.write_keyword("ENGINE");
29029        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
29030            self.write("=");
29031        } else {
29032            self.write(" = ");
29033        }
29034        self.generate_expression(&e.this)?;
29035        Ok(())
29036    }
29037
29038    fn generate_enviroment_property(&mut self, e: &EnviromentProperty) -> Result<()> {
29039        // ENVIRONMENT (expressions)
29040        self.write_keyword("ENVIRONMENT");
29041        self.write(" (");
29042        for (i, expr) in e.expressions.iter().enumerate() {
29043            if i > 0 {
29044                self.write(", ");
29045            }
29046            self.generate_expression(expr)?;
29047        }
29048        self.write(")");
29049        Ok(())
29050    }
29051
29052    fn generate_ephemeral_column_constraint(
29053        &mut self,
29054        e: &EphemeralColumnConstraint,
29055    ) -> Result<()> {
29056        // MySQL: EPHEMERAL [expr]
29057        self.write_keyword("EPHEMERAL");
29058        if let Some(this) = &e.this {
29059            self.write_space();
29060            self.generate_expression(this)?;
29061        }
29062        Ok(())
29063    }
29064
29065    fn generate_equal_null(&mut self, e: &EqualNull) -> Result<()> {
29066        // Snowflake: EQUAL_NULL(a, b)
29067        self.write_keyword("EQUAL_NULL");
29068        self.write("(");
29069        self.generate_expression(&e.this)?;
29070        self.write(", ");
29071        self.generate_expression(&e.expression)?;
29072        self.write(")");
29073        Ok(())
29074    }
29075
29076    fn generate_euclidean_distance(&mut self, e: &EuclideanDistance) -> Result<()> {
29077        use crate::dialects::DialectType;
29078
29079        // PostgreSQL uses <-> operator syntax
29080        match self.config.dialect {
29081            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
29082                self.generate_expression(&e.this)?;
29083                self.write(" <-> ");
29084                self.generate_expression(&e.expression)?;
29085            }
29086            _ => {
29087                // Other dialects use EUCLIDEAN_DISTANCE function
29088                self.write_keyword("EUCLIDEAN_DISTANCE");
29089                self.write("(");
29090                self.generate_expression(&e.this)?;
29091                self.write(", ");
29092                self.generate_expression(&e.expression)?;
29093                self.write(")");
29094            }
29095        }
29096        Ok(())
29097    }
29098
29099    fn generate_execute_as_property(&mut self, e: &ExecuteAsProperty) -> Result<()> {
29100        // EXECUTE AS CALLER|OWNER|user
29101        self.write_keyword("EXECUTE AS");
29102        self.write_space();
29103        self.generate_expression(&e.this)?;
29104        Ok(())
29105    }
29106
29107    fn generate_export(&mut self, e: &Export) -> Result<()> {
29108        // BigQuery: EXPORT DATA [WITH CONNECTION connection] OPTIONS (...) AS query
29109        self.write_keyword("EXPORT DATA");
29110        if let Some(connection) = &e.connection {
29111            self.write_space();
29112            self.write_keyword("WITH CONNECTION");
29113            self.write_space();
29114            self.generate_expression(connection)?;
29115        }
29116        if !e.options.is_empty() {
29117            self.write_space();
29118            self.generate_options_clause(&e.options)?;
29119        }
29120        self.write_space();
29121        self.write_keyword("AS");
29122        self.write_space();
29123        self.generate_expression(&e.this)?;
29124        Ok(())
29125    }
29126
29127    fn generate_external_property(&mut self, e: &ExternalProperty) -> Result<()> {
29128        // EXTERNAL [this]
29129        self.write_keyword("EXTERNAL");
29130        if let Some(this) = &e.this {
29131            self.write_space();
29132            self.generate_expression(this)?;
29133        }
29134        Ok(())
29135    }
29136
29137    fn generate_fallback_property(&mut self, e: &FallbackProperty) -> Result<()> {
29138        // Python: {no}FALLBACK{protection}
29139        if e.no.is_some() {
29140            self.write_keyword("NO ");
29141        }
29142        self.write_keyword("FALLBACK");
29143        if e.protection.is_some() {
29144            self.write_keyword(" PROTECTION");
29145        }
29146        Ok(())
29147    }
29148
29149    fn generate_farm_fingerprint(&mut self, e: &FarmFingerprint) -> Result<()> {
29150        // BigQuery: FARM_FINGERPRINT(value)
29151        self.write_keyword("FARM_FINGERPRINT");
29152        self.write("(");
29153        for (i, expr) in e.expressions.iter().enumerate() {
29154            if i > 0 {
29155                self.write(", ");
29156            }
29157            self.generate_expression(expr)?;
29158        }
29159        self.write(")");
29160        Ok(())
29161    }
29162
29163    fn generate_features_at_time(&mut self, e: &FeaturesAtTime) -> Result<()> {
29164        // BigQuery ML: FEATURES_AT_TIME(feature_view, time, [num_rows], [ignore_feature_nulls])
29165        self.write_keyword("FEATURES_AT_TIME");
29166        self.write("(");
29167        self.generate_expression(&e.this)?;
29168        if let Some(time) = &e.time {
29169            self.write(", ");
29170            self.generate_expression(time)?;
29171        }
29172        if let Some(num_rows) = &e.num_rows {
29173            self.write(", ");
29174            self.generate_expression(num_rows)?;
29175        }
29176        if let Some(ignore_nulls) = &e.ignore_feature_nulls {
29177            self.write(", ");
29178            self.generate_expression(ignore_nulls)?;
29179        }
29180        self.write(")");
29181        Ok(())
29182    }
29183
29184    fn generate_fetch(&mut self, e: &Fetch) -> Result<()> {
29185        // For dialects that prefer LIMIT, convert simple FETCH to LIMIT
29186        let use_limit = !e.percent
29187            && !e.with_ties
29188            && e.count.is_some()
29189            && matches!(
29190                self.config.dialect,
29191                Some(DialectType::Spark)
29192                    | Some(DialectType::Hive)
29193                    | Some(DialectType::DuckDB)
29194                    | Some(DialectType::SQLite)
29195                    | Some(DialectType::MySQL)
29196                    | Some(DialectType::BigQuery)
29197                    | Some(DialectType::Databricks)
29198                    | Some(DialectType::StarRocks)
29199                    | Some(DialectType::Doris)
29200                    | Some(DialectType::Athena)
29201                    | Some(DialectType::ClickHouse)
29202            );
29203
29204        if use_limit {
29205            self.write_keyword("LIMIT");
29206            self.write_space();
29207            self.generate_expression(e.count.as_ref().unwrap())?;
29208            return Ok(());
29209        }
29210
29211        // Python: FETCH direction count limit_options
29212        self.write_keyword("FETCH");
29213        if !e.direction.is_empty() {
29214            self.write_space();
29215            self.write_keyword(&e.direction);
29216        }
29217        if let Some(count) = &e.count {
29218            self.write_space();
29219            self.generate_expression(count)?;
29220        }
29221        // Generate PERCENT, ROWS, WITH TIES/ONLY
29222        if e.percent {
29223            self.write_keyword(" PERCENT");
29224        }
29225        if e.rows {
29226            self.write_keyword(" ROWS");
29227        }
29228        if e.with_ties {
29229            self.write_keyword(" WITH TIES");
29230        } else if e.rows {
29231            self.write_keyword(" ONLY");
29232        } else {
29233            self.write_keyword(" ROWS ONLY");
29234        }
29235        Ok(())
29236    }
29237
29238    fn generate_file_format_property(&mut self, e: &FileFormatProperty) -> Result<()> {
29239        // For Hive format: STORED AS this or STORED AS INPUTFORMAT x OUTPUTFORMAT y
29240        // For Spark/Databricks without hive_format: USING this
29241        // For Snowflake/others: FILE_FORMAT = this or FILE_FORMAT = (expressions)
29242        if e.hive_format.is_some() {
29243            // Hive format: STORED AS ...
29244            self.write_keyword("STORED AS");
29245            self.write_space();
29246            if let Some(this) = &e.this {
29247                // Uppercase the format name (e.g., parquet -> PARQUET)
29248                if let Expression::Identifier(id) = this.as_ref() {
29249                    self.write_keyword(&id.name.to_ascii_uppercase());
29250                } else {
29251                    self.generate_expression(this)?;
29252                }
29253            }
29254        } else if matches!(self.config.dialect, Some(DialectType::Hive)) {
29255            // Hive: STORED AS format
29256            self.write_keyword("STORED AS");
29257            self.write_space();
29258            if let Some(this) = &e.this {
29259                if let Expression::Identifier(id) = this.as_ref() {
29260                    self.write_keyword(&id.name.to_ascii_uppercase());
29261                } else {
29262                    self.generate_expression(this)?;
29263                }
29264            }
29265        } else if matches!(
29266            self.config.dialect,
29267            Some(DialectType::Spark) | Some(DialectType::Databricks)
29268        ) {
29269            // Spark/Databricks: USING format (e.g., USING DELTA)
29270            self.write_keyword("USING");
29271            self.write_space();
29272            if let Some(this) = &e.this {
29273                self.generate_expression(this)?;
29274            }
29275        } else {
29276            // Snowflake/standard format
29277            self.write_keyword("FILE_FORMAT");
29278            self.write(" = ");
29279            if let Some(this) = &e.this {
29280                self.generate_expression(this)?;
29281            } else if !e.expressions.is_empty() {
29282                self.write("(");
29283                for (i, expr) in e.expressions.iter().enumerate() {
29284                    if i > 0 {
29285                        self.write(", ");
29286                    }
29287                    self.generate_expression(expr)?;
29288                }
29289                self.write(")");
29290            }
29291        }
29292        Ok(())
29293    }
29294
29295    fn generate_filter(&mut self, e: &Filter) -> Result<()> {
29296        // agg_func FILTER(WHERE condition)
29297        self.generate_expression(&e.this)?;
29298        self.write_space();
29299        self.write_keyword("FILTER");
29300        self.write("(");
29301        self.write_keyword("WHERE");
29302        self.write_space();
29303        self.generate_expression(&e.expression)?;
29304        self.write(")");
29305        Ok(())
29306    }
29307
29308    fn generate_float64(&mut self, e: &Float64) -> Result<()> {
29309        // FLOAT64(this) or FLOAT64(this, expression)
29310        self.write_keyword("FLOAT64");
29311        self.write("(");
29312        self.generate_expression(&e.this)?;
29313        if let Some(expr) = &e.expression {
29314            self.write(", ");
29315            self.generate_expression(expr)?;
29316        }
29317        self.write(")");
29318        Ok(())
29319    }
29320
29321    fn generate_for_in(&mut self, e: &ForIn) -> Result<()> {
29322        // FOR this DO expression
29323        self.write_keyword("FOR");
29324        self.write_space();
29325        self.generate_expression(&e.this)?;
29326        self.write_space();
29327        self.write_keyword("DO");
29328        self.write_space();
29329        self.generate_expression(&e.expression)?;
29330        Ok(())
29331    }
29332
29333    fn generate_foreign_key(&mut self, e: &ForeignKey) -> Result<()> {
29334        // FOREIGN KEY (cols) REFERENCES table(cols) ON DELETE action ON UPDATE action
29335        self.write_keyword("FOREIGN KEY");
29336        if !e.expressions.is_empty() {
29337            self.write(" (");
29338            for (i, expr) in e.expressions.iter().enumerate() {
29339                if i > 0 {
29340                    self.write(", ");
29341                }
29342                self.generate_expression(expr)?;
29343            }
29344            self.write(")");
29345        }
29346        if let Some(reference) = &e.reference {
29347            self.write_space();
29348            self.generate_expression(reference)?;
29349        }
29350        if let Some(delete) = &e.delete {
29351            self.write_space();
29352            self.write_keyword("ON DELETE");
29353            self.write_space();
29354            self.generate_expression(delete)?;
29355        }
29356        if let Some(update) = &e.update {
29357            self.write_space();
29358            self.write_keyword("ON UPDATE");
29359            self.write_space();
29360            self.generate_expression(update)?;
29361        }
29362        if !e.options.is_empty() {
29363            self.write_space();
29364            for (i, opt) in e.options.iter().enumerate() {
29365                if i > 0 {
29366                    self.write_space();
29367                }
29368                self.generate_expression(opt)?;
29369            }
29370        }
29371        Ok(())
29372    }
29373
29374    fn generate_format(&mut self, e: &Format) -> Result<()> {
29375        // FORMAT(this, expressions...)
29376        self.write_keyword("FORMAT");
29377        self.write("(");
29378        self.generate_expression(&e.this)?;
29379        for expr in &e.expressions {
29380            self.write(", ");
29381            self.generate_expression(expr)?;
29382        }
29383        self.write(")");
29384        Ok(())
29385    }
29386
29387    fn generate_format_phrase(&mut self, e: &FormatPhrase) -> Result<()> {
29388        // Teradata: column (FORMAT 'format_string')
29389        self.generate_expression(&e.this)?;
29390        self.write(" (");
29391        self.write_keyword("FORMAT");
29392        self.write(" '");
29393        self.write(&e.format);
29394        self.write("')");
29395        Ok(())
29396    }
29397
29398    fn generate_freespace_property(&mut self, e: &FreespaceProperty) -> Result<()> {
29399        // Python: FREESPACE=this[PERCENT]
29400        self.write_keyword("FREESPACE");
29401        self.write("=");
29402        self.generate_expression(&e.this)?;
29403        if e.percent.is_some() {
29404            self.write_keyword(" PERCENT");
29405        }
29406        Ok(())
29407    }
29408
29409    fn generate_from(&mut self, e: &From) -> Result<()> {
29410        // Python: return f"{self.seg('FROM')} {self.sql(expression, 'this')}"
29411        self.write_keyword("FROM");
29412        self.write_space();
29413
29414        // BigQuery, Hive, Spark, Databricks, SQLite, and ClickHouse prefer explicit CROSS JOIN over comma syntax
29415        // But keep commas when TABLESAMPLE is present
29416        // Also keep commas when the source dialect is Generic/None and target is one of these dialects
29417        use crate::dialects::DialectType;
29418        let has_tablesample = e
29419            .expressions
29420            .iter()
29421            .any(|expr| matches!(expr, Expression::TableSample(_)));
29422        let is_cross_join_dialect = matches!(
29423            self.config.dialect,
29424            Some(DialectType::BigQuery)
29425                | Some(DialectType::Hive)
29426                | Some(DialectType::Spark)
29427                | Some(DialectType::Databricks)
29428                | Some(DialectType::SQLite)
29429                | Some(DialectType::ClickHouse)
29430        );
29431        let source_is_same_as_target2 = self.config.source_dialect.is_some()
29432            && self.config.source_dialect == self.config.dialect;
29433        let source_is_cross_join_dialect2 = matches!(
29434            self.config.source_dialect,
29435            Some(DialectType::BigQuery)
29436                | Some(DialectType::Hive)
29437                | Some(DialectType::Spark)
29438                | Some(DialectType::Databricks)
29439                | Some(DialectType::SQLite)
29440                | Some(DialectType::ClickHouse)
29441        );
29442        let use_cross_join = !has_tablesample
29443            && is_cross_join_dialect
29444            && (source_is_same_as_target2
29445                || source_is_cross_join_dialect2
29446                || self.config.source_dialect.is_none());
29447
29448        // Snowflake wraps standalone VALUES in FROM clause with parentheses
29449        let wrap_values_in_parens = matches!(self.config.dialect, Some(DialectType::Snowflake));
29450
29451        for (i, expr) in e.expressions.iter().enumerate() {
29452            if i > 0 {
29453                if use_cross_join {
29454                    self.write(" CROSS JOIN ");
29455                } else {
29456                    self.write(", ");
29457                }
29458            }
29459            if wrap_values_in_parens && matches!(expr, Expression::Values(_)) {
29460                self.write("(");
29461                self.generate_expression(expr)?;
29462                self.write(")");
29463            } else {
29464                self.generate_expression(expr)?;
29465            }
29466            // Output leading comments that were on the table name before FROM
29467            // (e.g., FROM \n/* comment */\n tbl PIVOT(...) -> ... PIVOT(...) /* comment */)
29468            let leading = Self::extract_table_leading_comments(expr);
29469            for comment in &leading {
29470                self.write_space();
29471                self.write_formatted_comment(comment);
29472            }
29473        }
29474        Ok(())
29475    }
29476
29477    /// Extract leading_comments from a table expression (possibly wrapped in PIVOT/UNPIVOT)
29478    fn extract_table_leading_comments(expr: &Expression) -> Vec<String> {
29479        match expr {
29480            Expression::Table(t) => t.leading_comments.clone(),
29481            Expression::Pivot(p) => {
29482                if let Expression::Table(t) = &p.this {
29483                    t.leading_comments.clone()
29484                } else {
29485                    Vec::new()
29486                }
29487            }
29488            _ => Vec::new(),
29489        }
29490    }
29491
29492    fn generate_from_base(&mut self, e: &FromBase) -> Result<()> {
29493        // FROM_BASE(this, expression) - convert from base N
29494        self.write_keyword("FROM_BASE");
29495        self.write("(");
29496        self.generate_expression(&e.this)?;
29497        self.write(", ");
29498        self.generate_expression(&e.expression)?;
29499        self.write(")");
29500        Ok(())
29501    }
29502
29503    fn generate_from_time_zone(&mut self, e: &FromTimeZone) -> Result<()> {
29504        // this AT TIME ZONE zone AT TIME ZONE 'UTC'
29505        self.generate_expression(&e.this)?;
29506        if let Some(zone) = &e.zone {
29507            self.write_space();
29508            self.write_keyword("AT TIME ZONE");
29509            self.write_space();
29510            self.generate_expression(zone)?;
29511            self.write_space();
29512            self.write_keyword("AT TIME ZONE");
29513            self.write(" 'UTC'");
29514        }
29515        Ok(())
29516    }
29517
29518    fn generate_gap_fill(&mut self, e: &GapFill) -> Result<()> {
29519        // GAP_FILL(this, ts_column, bucket_width, ...)
29520        self.write_keyword("GAP_FILL");
29521        self.write("(");
29522        self.generate_expression(&e.this)?;
29523        if let Some(ts_column) = &e.ts_column {
29524            self.write(", ");
29525            self.generate_expression(ts_column)?;
29526        }
29527        if let Some(bucket_width) = &e.bucket_width {
29528            self.write(", ");
29529            self.generate_expression(bucket_width)?;
29530        }
29531        if let Some(partitioning_columns) = &e.partitioning_columns {
29532            self.write(", ");
29533            self.generate_expression(partitioning_columns)?;
29534        }
29535        if let Some(value_columns) = &e.value_columns {
29536            self.write(", ");
29537            self.generate_expression(value_columns)?;
29538        }
29539        self.write(")");
29540        Ok(())
29541    }
29542
29543    fn generate_generate_date_array(&mut self, e: &GenerateDateArray) -> Result<()> {
29544        // GENERATE_DATE_ARRAY(start, end, step)
29545        self.write_keyword("GENERATE_DATE_ARRAY");
29546        self.write("(");
29547        let mut first = true;
29548        if let Some(start) = &e.start {
29549            self.generate_expression(start)?;
29550            first = false;
29551        }
29552        if let Some(end) = &e.end {
29553            if !first {
29554                self.write(", ");
29555            }
29556            self.generate_expression(end)?;
29557            first = false;
29558        }
29559        if let Some(step) = &e.step {
29560            if !first {
29561                self.write(", ");
29562            }
29563            self.generate_expression(step)?;
29564        }
29565        self.write(")");
29566        Ok(())
29567    }
29568
29569    fn generate_generate_embedding(&mut self, e: &GenerateEmbedding) -> Result<()> {
29570        // ML.GENERATE_EMBEDDING(model, content, params)
29571        self.write_keyword("ML.GENERATE_EMBEDDING");
29572        self.write("(");
29573        self.generate_expression(&e.this)?;
29574        self.write(", ");
29575        self.generate_expression(&e.expression)?;
29576        if let Some(params) = &e.params_struct {
29577            self.write(", ");
29578            self.generate_expression(params)?;
29579        }
29580        self.write(")");
29581        Ok(())
29582    }
29583
29584    fn generate_generate_series(&mut self, e: &GenerateSeries) -> Result<()> {
29585        // Dialect-specific function name
29586        let fn_name = match self.config.dialect {
29587            Some(DialectType::Presto)
29588            | Some(DialectType::Trino)
29589            | Some(DialectType::Athena)
29590            | Some(DialectType::Spark)
29591            | Some(DialectType::Databricks)
29592            | Some(DialectType::Hive) => "SEQUENCE",
29593            _ => "GENERATE_SERIES",
29594        };
29595        self.write_keyword(fn_name);
29596        self.write("(");
29597        let mut first = true;
29598        if let Some(start) = &e.start {
29599            self.generate_expression(start)?;
29600            first = false;
29601        }
29602        if let Some(end) = &e.end {
29603            if !first {
29604                self.write(", ");
29605            }
29606            self.generate_expression(end)?;
29607            first = false;
29608        }
29609        if let Some(step) = &e.step {
29610            if !first {
29611                self.write(", ");
29612            }
29613            // For Presto/Trino: convert WEEK intervals to DAY multiples
29614            // e.g., INTERVAL '1' WEEK -> (1 * INTERVAL '7' DAY)
29615            if matches!(
29616                self.config.dialect,
29617                Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
29618            ) {
29619                if let Some(converted) = self.convert_week_interval_to_day(step) {
29620                    self.generate_expression(&converted)?;
29621                } else {
29622                    self.generate_expression(step)?;
29623                }
29624            } else {
29625                self.generate_expression(step)?;
29626            }
29627        }
29628        self.write(")");
29629        Ok(())
29630    }
29631
29632    /// Convert a WEEK interval to a DAY-based multiplication expression for Presto/Trino.
29633    /// INTERVAL N WEEK -> (N * INTERVAL '7' DAY)
29634    fn convert_week_interval_to_day(&self, expr: &Expression) -> Option<Expression> {
29635        use crate::expressions::*;
29636        if let Expression::Interval(ref iv) = expr {
29637            // Check for structured WEEK unit
29638            let (is_week, count_str) = if let Some(IntervalUnitSpec::Simple {
29639                unit: IntervalUnit::Week,
29640                ..
29641            }) = &iv.unit
29642            {
29643                // Value is in iv.this
29644                let count = match &iv.this {
29645                    Some(Expression::Literal(lit)) => match lit.as_ref() {
29646                        Literal::String(s) | Literal::Number(s) => s.clone(),
29647                        _ => return None,
29648                    },
29649                    _ => return None,
29650                };
29651                (true, count)
29652            } else if iv.unit.is_none() {
29653                // Check for string-encoded interval like "1 WEEK"
29654                if let Some(Expression::Literal(lit)) = &iv.this {
29655                    if let Literal::String(s) = lit.as_ref() {
29656                        let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
29657                        if parts.len() == 2 && parts[1].eq_ignore_ascii_case("WEEK") {
29658                            (true, parts[0].to_string())
29659                        } else {
29660                            (false, String::new())
29661                        }
29662                    } else {
29663                        (false, String::new())
29664                    }
29665                } else {
29666                    (false, String::new())
29667                }
29668            } else {
29669                (false, String::new())
29670            };
29671
29672            if is_week {
29673                // Build: (N * INTERVAL '7' DAY)
29674                let count_expr = Expression::Literal(Box::new(Literal::Number(count_str)));
29675                let day_interval = Expression::Interval(Box::new(Interval {
29676                    this: Some(Expression::Literal(Box::new(Literal::String(
29677                        "7".to_string(),
29678                    )))),
29679                    unit: Some(IntervalUnitSpec::Simple {
29680                        unit: IntervalUnit::Day,
29681                        use_plural: false,
29682                    }),
29683                }));
29684                let mul = Expression::Mul(Box::new(BinaryOp {
29685                    left: count_expr,
29686                    right: day_interval,
29687                    left_comments: vec![],
29688                    operator_comments: vec![],
29689                    trailing_comments: vec![],
29690                    inferred_type: None,
29691                }));
29692                return Some(Expression::Paren(Box::new(Paren {
29693                    this: mul,
29694                    trailing_comments: vec![],
29695                })));
29696            }
29697        }
29698        None
29699    }
29700
29701    fn generate_generate_timestamp_array(&mut self, e: &GenerateTimestampArray) -> Result<()> {
29702        // GENERATE_TIMESTAMP_ARRAY(start, end, step)
29703        self.write_keyword("GENERATE_TIMESTAMP_ARRAY");
29704        self.write("(");
29705        let mut first = true;
29706        if let Some(start) = &e.start {
29707            self.generate_expression(start)?;
29708            first = false;
29709        }
29710        if let Some(end) = &e.end {
29711            if !first {
29712                self.write(", ");
29713            }
29714            self.generate_expression(end)?;
29715            first = false;
29716        }
29717        if let Some(step) = &e.step {
29718            if !first {
29719                self.write(", ");
29720            }
29721            self.generate_expression(step)?;
29722        }
29723        self.write(")");
29724        Ok(())
29725    }
29726
29727    fn generate_generated_as_identity_column_constraint(
29728        &mut self,
29729        e: &GeneratedAsIdentityColumnConstraint,
29730    ) -> Result<()> {
29731        use crate::dialects::DialectType;
29732
29733        // For Snowflake, use AUTOINCREMENT START x INCREMENT y syntax
29734        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
29735            self.write_keyword("AUTOINCREMENT");
29736            if let Some(start) = &e.start {
29737                self.write_keyword(" START ");
29738                self.generate_expression(start)?;
29739            }
29740            if let Some(increment) = &e.increment {
29741                self.write_keyword(" INCREMENT ");
29742                self.generate_expression(increment)?;
29743            }
29744            return Ok(());
29745        }
29746
29747        // Python: GENERATED [ALWAYS|BY DEFAULT [ON NULL]] AS IDENTITY [(start, increment, ...)]
29748        self.write_keyword("GENERATED");
29749        if let Some(this) = &e.this {
29750            // Check if it's a truthy boolean expression
29751            if let Expression::Boolean(b) = this.as_ref() {
29752                if b.value {
29753                    self.write_keyword(" ALWAYS");
29754                } else {
29755                    self.write_keyword(" BY DEFAULT");
29756                    if e.on_null.is_some() {
29757                        self.write_keyword(" ON NULL");
29758                    }
29759                }
29760            } else {
29761                self.write_keyword(" ALWAYS");
29762            }
29763        }
29764        self.write_keyword(" AS IDENTITY");
29765        // Add sequence options if any
29766        let has_options = e.start.is_some()
29767            || e.increment.is_some()
29768            || e.minvalue.is_some()
29769            || e.maxvalue.is_some();
29770        if has_options {
29771            self.write(" (");
29772            let mut first = true;
29773            if let Some(start) = &e.start {
29774                self.write_keyword("START WITH ");
29775                self.generate_expression(start)?;
29776                first = false;
29777            }
29778            if let Some(increment) = &e.increment {
29779                if !first {
29780                    self.write(" ");
29781                }
29782                self.write_keyword("INCREMENT BY ");
29783                self.generate_expression(increment)?;
29784                first = false;
29785            }
29786            if let Some(minvalue) = &e.minvalue {
29787                if !first {
29788                    self.write(" ");
29789                }
29790                self.write_keyword("MINVALUE ");
29791                self.generate_expression(minvalue)?;
29792                first = false;
29793            }
29794            if let Some(maxvalue) = &e.maxvalue {
29795                if !first {
29796                    self.write(" ");
29797                }
29798                self.write_keyword("MAXVALUE ");
29799                self.generate_expression(maxvalue)?;
29800            }
29801            self.write(")");
29802        }
29803        Ok(())
29804    }
29805
29806    fn generate_generated_as_row_column_constraint(
29807        &mut self,
29808        e: &GeneratedAsRowColumnConstraint,
29809    ) -> Result<()> {
29810        // Python: GENERATED ALWAYS AS ROW START|END [HIDDEN]
29811        self.write_keyword("GENERATED ALWAYS AS ROW ");
29812        if e.start.is_some() {
29813            self.write_keyword("START");
29814        } else {
29815            self.write_keyword("END");
29816        }
29817        if e.hidden.is_some() {
29818            self.write_keyword(" HIDDEN");
29819        }
29820        Ok(())
29821    }
29822
29823    fn generate_get(&mut self, e: &Get) -> Result<()> {
29824        // GET this target properties
29825        self.write_keyword("GET");
29826        self.write_space();
29827        self.generate_expression(&e.this)?;
29828        if let Some(target) = &e.target {
29829            self.write_space();
29830            self.generate_expression(target)?;
29831        }
29832        for prop in &e.properties {
29833            self.write_space();
29834            self.generate_expression(prop)?;
29835        }
29836        Ok(())
29837    }
29838
29839    fn generate_get_extract(&mut self, e: &GetExtract) -> Result<()> {
29840        // GetExtract generates bracket access: this[expression]
29841        self.generate_expression(&e.this)?;
29842        self.write("[");
29843        self.generate_expression(&e.expression)?;
29844        self.write("]");
29845        Ok(())
29846    }
29847
29848    fn generate_getbit(&mut self, e: &Getbit) -> Result<()> {
29849        // GETBIT(this, expression) or GET_BIT(this, expression)
29850        self.write_keyword("GETBIT");
29851        self.write("(");
29852        self.generate_expression(&e.this)?;
29853        self.write(", ");
29854        self.generate_expression(&e.expression)?;
29855        self.write(")");
29856        Ok(())
29857    }
29858
29859    fn generate_grant_principal(&mut self, e: &GrantPrincipal) -> Result<()> {
29860        // [ROLE|GROUP|SHARE] name (e.g., "ROLE admin", "GROUP qa_users", "SHARE s1", or just "user1")
29861        if e.is_role {
29862            self.write_keyword("ROLE");
29863            self.write_space();
29864        } else if e.is_group {
29865            self.write_keyword("GROUP");
29866            self.write_space();
29867        } else if e.is_share {
29868            self.write_keyword("SHARE");
29869            self.write_space();
29870        }
29871        self.write(&e.name.name);
29872        Ok(())
29873    }
29874
29875    fn generate_grant_privilege(&mut self, e: &GrantPrivilege) -> Result<()> {
29876        // privilege(columns) or just privilege
29877        self.generate_expression(&e.this)?;
29878        if !e.expressions.is_empty() {
29879            self.write("(");
29880            for (i, expr) in e.expressions.iter().enumerate() {
29881                if i > 0 {
29882                    self.write(", ");
29883                }
29884                self.generate_expression(expr)?;
29885            }
29886            self.write(")");
29887        }
29888        Ok(())
29889    }
29890
29891    fn generate_group(&mut self, e: &Group) -> Result<()> {
29892        // Python handles GROUP BY ALL/DISTINCT modifiers and grouping expressions
29893        self.write_keyword("GROUP BY");
29894        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
29895        match e.all {
29896            Some(true) => {
29897                self.write_space();
29898                self.write_keyword("ALL");
29899            }
29900            Some(false) => {
29901                self.write_space();
29902                self.write_keyword("DISTINCT");
29903            }
29904            None => {}
29905        }
29906        if !e.expressions.is_empty() {
29907            self.write_space();
29908            for (i, expr) in e.expressions.iter().enumerate() {
29909                if i > 0 {
29910                    self.write(", ");
29911                }
29912                self.generate_expression(expr)?;
29913            }
29914        }
29915        // Handle CUBE, ROLLUP, GROUPING SETS
29916        if let Some(cube) = &e.cube {
29917            if !e.expressions.is_empty() {
29918                self.write(", ");
29919            } else {
29920                self.write_space();
29921            }
29922            self.generate_expression(cube)?;
29923        }
29924        if let Some(rollup) = &e.rollup {
29925            if !e.expressions.is_empty() || e.cube.is_some() {
29926                self.write(", ");
29927            } else {
29928                self.write_space();
29929            }
29930            self.generate_expression(rollup)?;
29931        }
29932        if let Some(grouping_sets) = &e.grouping_sets {
29933            if !e.expressions.is_empty() || e.cube.is_some() || e.rollup.is_some() {
29934                self.write(", ");
29935            } else {
29936                self.write_space();
29937            }
29938            self.generate_expression(grouping_sets)?;
29939        }
29940        if let Some(totals) = &e.totals {
29941            self.write_space();
29942            self.write_keyword("WITH TOTALS");
29943            self.generate_expression(totals)?;
29944        }
29945        Ok(())
29946    }
29947
29948    fn generate_group_by(&mut self, e: &GroupBy) -> Result<()> {
29949        // GROUP BY expressions
29950        self.write_keyword("GROUP BY");
29951        // Handle ALL/DISTINCT modifier: Some(true) = ALL, Some(false) = DISTINCT
29952        match e.all {
29953            Some(true) => {
29954                self.write_space();
29955                self.write_keyword("ALL");
29956            }
29957            Some(false) => {
29958                self.write_space();
29959                self.write_keyword("DISTINCT");
29960            }
29961            None => {}
29962        }
29963
29964        // Check for trailing WITH CUBE or WITH ROLLUP (Hive/MySQL syntax)
29965        // These are represented as Cube/Rollup expressions with empty expressions at the end
29966        let mut trailing_cube = false;
29967        let mut trailing_rollup = false;
29968        let mut regular_expressions: Vec<&Expression> = Vec::new();
29969
29970        for expr in &e.expressions {
29971            match expr {
29972                Expression::Cube(c) if c.expressions.is_empty() => {
29973                    trailing_cube = true;
29974                }
29975                Expression::Rollup(r) if r.expressions.is_empty() => {
29976                    trailing_rollup = true;
29977                }
29978                _ => {
29979                    regular_expressions.push(expr);
29980                }
29981            }
29982        }
29983
29984        // In pretty mode, put columns on separate lines
29985        if self.config.pretty {
29986            self.write_newline();
29987            self.indent_level += 1;
29988            for (i, expr) in regular_expressions.iter().enumerate() {
29989                if i > 0 {
29990                    self.write(",");
29991                    self.write_newline();
29992                }
29993                self.write_indent();
29994                self.generate_expression(expr)?;
29995            }
29996            self.indent_level -= 1;
29997        } else {
29998            self.write_space();
29999            for (i, expr) in regular_expressions.iter().enumerate() {
30000                if i > 0 {
30001                    self.write(", ");
30002                }
30003                self.generate_expression(expr)?;
30004            }
30005        }
30006
30007        // Output trailing WITH CUBE or WITH ROLLUP
30008        if trailing_cube {
30009            self.write_space();
30010            self.write_keyword("WITH CUBE");
30011        } else if trailing_rollup {
30012            self.write_space();
30013            self.write_keyword("WITH ROLLUP");
30014        }
30015
30016        // ClickHouse: WITH TOTALS
30017        if e.totals {
30018            self.write_space();
30019            self.write_keyword("WITH TOTALS");
30020        }
30021
30022        Ok(())
30023    }
30024
30025    fn generate_grouping(&mut self, e: &Grouping) -> Result<()> {
30026        // GROUPING(col1, col2, ...)
30027        self.write_keyword("GROUPING");
30028        self.write("(");
30029        for (i, expr) in e.expressions.iter().enumerate() {
30030            if i > 0 {
30031                self.write(", ");
30032            }
30033            self.generate_expression(expr)?;
30034        }
30035        self.write(")");
30036        Ok(())
30037    }
30038
30039    fn generate_grouping_id(&mut self, e: &GroupingId) -> Result<()> {
30040        // GROUPING_ID(col1, col2, ...)
30041        self.write_keyword("GROUPING_ID");
30042        self.write("(");
30043        for (i, expr) in e.expressions.iter().enumerate() {
30044            if i > 0 {
30045                self.write(", ");
30046            }
30047            self.generate_expression(expr)?;
30048        }
30049        self.write(")");
30050        Ok(())
30051    }
30052
30053    fn generate_grouping_sets(&mut self, e: &GroupingSets) -> Result<()> {
30054        // Python: return f"GROUPING SETS {self.wrap(grouping_sets)}"
30055        self.write_keyword("GROUPING SETS");
30056        self.write(" (");
30057        for (i, expr) in e.expressions.iter().enumerate() {
30058            if i > 0 {
30059                self.write(", ");
30060            }
30061            self.generate_expression(expr)?;
30062        }
30063        self.write(")");
30064        Ok(())
30065    }
30066
30067    fn generate_hash_agg(&mut self, e: &HashAgg) -> Result<()> {
30068        // HASH_AGG(this, expressions...)
30069        self.write_keyword("HASH_AGG");
30070        self.write("(");
30071        self.generate_expression(&e.this)?;
30072        for expr in &e.expressions {
30073            self.write(", ");
30074            self.generate_expression(expr)?;
30075        }
30076        self.write(")");
30077        Ok(())
30078    }
30079
30080    fn generate_having(&mut self, e: &Having) -> Result<()> {
30081        // Python: return f"{self.seg('HAVING')}{self.sep()}{this}"
30082        self.write_keyword("HAVING");
30083        self.write_space();
30084        self.generate_expression(&e.this)?;
30085        Ok(())
30086    }
30087
30088    fn generate_having_max(&mut self, e: &HavingMax) -> Result<()> {
30089        // Python: this HAVING MAX|MIN expression
30090        self.generate_expression(&e.this)?;
30091        self.write_space();
30092        self.write_keyword("HAVING");
30093        self.write_space();
30094        if e.max.is_some() {
30095            self.write_keyword("MAX");
30096        } else {
30097            self.write_keyword("MIN");
30098        }
30099        self.write_space();
30100        self.generate_expression(&e.expression)?;
30101        Ok(())
30102    }
30103
30104    fn generate_heredoc(&mut self, e: &Heredoc) -> Result<()> {
30105        use crate::dialects::DialectType;
30106        // DuckDB: convert dollar-tagged strings to single-quoted
30107        if matches!(self.config.dialect, Some(DialectType::DuckDB)) {
30108            // Extract the string content and output as single-quoted
30109            if let Expression::Literal(ref lit) = *e.this {
30110                if let Literal::String(ref s) = lit.as_ref() {
30111                    return self.generate_string_literal(s);
30112                }
30113            }
30114        }
30115        // PostgreSQL: preserve dollar-quoting
30116        if matches!(
30117            self.config.dialect,
30118            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
30119        ) {
30120            self.write("$");
30121            if let Some(tag) = &e.tag {
30122                self.generate_expression(tag)?;
30123            }
30124            self.write("$");
30125            self.generate_expression(&e.this)?;
30126            self.write("$");
30127            if let Some(tag) = &e.tag {
30128                self.generate_expression(tag)?;
30129            }
30130            self.write("$");
30131            return Ok(());
30132        }
30133        // Default: output as dollar-tagged
30134        self.write("$");
30135        if let Some(tag) = &e.tag {
30136            self.generate_expression(tag)?;
30137        }
30138        self.write("$");
30139        self.generate_expression(&e.this)?;
30140        self.write("$");
30141        if let Some(tag) = &e.tag {
30142            self.generate_expression(tag)?;
30143        }
30144        self.write("$");
30145        Ok(())
30146    }
30147
30148    fn generate_hex_encode(&mut self, e: &HexEncode) -> Result<()> {
30149        // HEX_ENCODE(this)
30150        self.write_keyword("HEX_ENCODE");
30151        self.write("(");
30152        self.generate_expression(&e.this)?;
30153        self.write(")");
30154        Ok(())
30155    }
30156
30157    fn generate_historical_data(&mut self, e: &HistoricalData) -> Result<()> {
30158        // Python: this (kind => expression)
30159        // Write the keyword (AT/BEFORE/END) directly to avoid quoting it as a reserved word
30160        match e.this.as_ref() {
30161            Expression::Identifier(id) => self.write(&id.name),
30162            other => self.generate_expression(other)?,
30163        }
30164        self.write(" (");
30165        self.write(&e.kind);
30166        self.write(" => ");
30167        self.generate_expression(&e.expression)?;
30168        self.write(")");
30169        Ok(())
30170    }
30171
30172    fn generate_hll(&mut self, e: &Hll) -> Result<()> {
30173        // HLL(this, expressions...)
30174        self.write_keyword("HLL");
30175        self.write("(");
30176        self.generate_expression(&e.this)?;
30177        for expr in &e.expressions {
30178            self.write(", ");
30179            self.generate_expression(expr)?;
30180        }
30181        self.write(")");
30182        Ok(())
30183    }
30184
30185    fn generate_in_out_column_constraint(&mut self, e: &InOutColumnConstraint) -> Result<()> {
30186        // Python: IN|OUT|IN OUT
30187        if e.input_.is_some() && e.output.is_some() {
30188            self.write_keyword("IN OUT");
30189        } else if e.input_.is_some() {
30190            self.write_keyword("IN");
30191        } else if e.output.is_some() {
30192            self.write_keyword("OUT");
30193        }
30194        Ok(())
30195    }
30196
30197    fn generate_include_property(&mut self, e: &IncludeProperty) -> Result<()> {
30198        // Python: INCLUDE this [column_def] [AS alias]
30199        self.write_keyword("INCLUDE");
30200        self.write_space();
30201        self.generate_expression(&e.this)?;
30202        if let Some(column_def) = &e.column_def {
30203            self.write_space();
30204            self.generate_expression(column_def)?;
30205        }
30206        if let Some(alias) = &e.alias {
30207            self.write_space();
30208            self.write_keyword("AS");
30209            self.write_space();
30210            self.write(alias);
30211        }
30212        Ok(())
30213    }
30214
30215    fn generate_index(&mut self, e: &Index) -> Result<()> {
30216        // [UNIQUE] [PRIMARY] [AMP] INDEX [name] [ON table] (params)
30217        if e.unique {
30218            self.write_keyword("UNIQUE");
30219            self.write_space();
30220        }
30221        if e.primary.is_some() {
30222            self.write_keyword("PRIMARY");
30223            self.write_space();
30224        }
30225        if e.amp.is_some() {
30226            self.write_keyword("AMP");
30227            self.write_space();
30228        }
30229        if e.table.is_none() {
30230            self.write_keyword("INDEX");
30231            self.write_space();
30232        }
30233        if let Some(name) = &e.this {
30234            self.generate_expression(name)?;
30235            self.write_space();
30236        }
30237        if let Some(table) = &e.table {
30238            self.write_keyword("ON");
30239            self.write_space();
30240            self.generate_expression(table)?;
30241        }
30242        if !e.params.is_empty() {
30243            self.write("(");
30244            for (i, param) in e.params.iter().enumerate() {
30245                if i > 0 {
30246                    self.write(", ");
30247                }
30248                self.generate_expression(param)?;
30249            }
30250            self.write(")");
30251        }
30252        Ok(())
30253    }
30254
30255    fn generate_index_column_constraint(&mut self, e: &IndexColumnConstraint) -> Result<()> {
30256        // Python: kind INDEX [this] [USING index_type] (expressions) [options]
30257        if let Some(kind) = &e.kind {
30258            self.write(kind);
30259            self.write_space();
30260        }
30261        self.write_keyword("INDEX");
30262        if let Some(this) = &e.this {
30263            self.write_space();
30264            self.generate_expression(this)?;
30265        }
30266        if let Some(index_type) = &e.index_type {
30267            self.write_space();
30268            self.write_keyword("USING");
30269            self.write_space();
30270            self.generate_expression(index_type)?;
30271        }
30272        if !e.expressions.is_empty() {
30273            self.write(" (");
30274            for (i, expr) in e.expressions.iter().enumerate() {
30275                if i > 0 {
30276                    self.write(", ");
30277                }
30278                self.generate_expression(expr)?;
30279            }
30280            self.write(")");
30281        }
30282        for opt in &e.options {
30283            self.write_space();
30284            self.generate_expression(opt)?;
30285        }
30286        Ok(())
30287    }
30288
30289    fn generate_index_constraint_option(&mut self, e: &IndexConstraintOption) -> Result<()> {
30290        // Python: KEY_BLOCK_SIZE = x | USING x | WITH PARSER x | COMMENT x | visible | engine_attr | secondary_engine_attr
30291        if let Some(key_block_size) = &e.key_block_size {
30292            self.write_keyword("KEY_BLOCK_SIZE");
30293            self.write(" = ");
30294            self.generate_expression(key_block_size)?;
30295        } else if let Some(using) = &e.using {
30296            self.write_keyword("USING");
30297            self.write_space();
30298            self.generate_expression(using)?;
30299        } else if let Some(parser) = &e.parser {
30300            self.write_keyword("WITH PARSER");
30301            self.write_space();
30302            self.generate_expression(parser)?;
30303        } else if let Some(comment) = &e.comment {
30304            self.write_keyword("COMMENT");
30305            self.write_space();
30306            self.generate_expression(comment)?;
30307        } else if let Some(visible) = &e.visible {
30308            self.generate_expression(visible)?;
30309        } else if let Some(engine_attr) = &e.engine_attr {
30310            self.write_keyword("ENGINE_ATTRIBUTE");
30311            self.write(" = ");
30312            self.generate_expression(engine_attr)?;
30313        } else if let Some(secondary_engine_attr) = &e.secondary_engine_attr {
30314            self.write_keyword("SECONDARY_ENGINE_ATTRIBUTE");
30315            self.write(" = ");
30316            self.generate_expression(secondary_engine_attr)?;
30317        }
30318        Ok(())
30319    }
30320
30321    fn generate_index_parameters(&mut self, e: &IndexParameters) -> Result<()> {
30322        // Python: [USING using] (columns) [PARTITION BY partition_by] [where] [INCLUDE (include)] [WITH (with_storage)] [USING INDEX TABLESPACE tablespace]
30323        if let Some(using) = &e.using {
30324            self.write_keyword("USING");
30325            self.write_space();
30326            self.generate_expression(using)?;
30327        }
30328        if !e.columns.is_empty() {
30329            self.write("(");
30330            for (i, col) in e.columns.iter().enumerate() {
30331                if i > 0 {
30332                    self.write(", ");
30333                }
30334                self.generate_expression(col)?;
30335            }
30336            self.write(")");
30337        }
30338        if let Some(partition_by) = &e.partition_by {
30339            self.write_space();
30340            self.write_keyword("PARTITION BY");
30341            self.write_space();
30342            self.generate_expression(partition_by)?;
30343        }
30344        if let Some(where_) = &e.where_ {
30345            self.write_space();
30346            self.generate_expression(where_)?;
30347        }
30348        if let Some(include) = &e.include {
30349            self.write_space();
30350            self.write_keyword("INCLUDE");
30351            self.write(" (");
30352            self.generate_expression(include)?;
30353            self.write(")");
30354        }
30355        if let Some(with_storage) = &e.with_storage {
30356            self.write_space();
30357            self.write_keyword("WITH");
30358            self.write(" (");
30359            self.generate_expression(with_storage)?;
30360            self.write(")");
30361        }
30362        if let Some(tablespace) = &e.tablespace {
30363            self.write_space();
30364            self.write_keyword("USING INDEX TABLESPACE");
30365            self.write_space();
30366            self.generate_expression(tablespace)?;
30367        }
30368        Ok(())
30369    }
30370
30371    fn generate_index_table_hint(&mut self, e: &IndexTableHint) -> Result<()> {
30372        // Python: this INDEX [FOR target] (expressions)
30373        // Write hint type (USE/IGNORE/FORCE) as keyword, not through generate_expression
30374        // to avoid quoting reserved keywords like IGNORE, FORCE, JOIN
30375        if let Expression::Identifier(id) = &*e.this {
30376            self.write_keyword(&id.name);
30377        } else {
30378            self.generate_expression(&e.this)?;
30379        }
30380        self.write_space();
30381        self.write_keyword("INDEX");
30382        if let Some(target) = &e.target {
30383            self.write_space();
30384            self.write_keyword("FOR");
30385            self.write_space();
30386            if let Expression::Identifier(id) = &**target {
30387                self.write_keyword(&id.name);
30388            } else {
30389                self.generate_expression(target)?;
30390            }
30391        }
30392        // Always output parentheses (even if empty, e.g. USE INDEX ())
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        Ok(())
30402    }
30403
30404    fn generate_inherits_property(&mut self, e: &InheritsProperty) -> Result<()> {
30405        // INHERITS (table1, table2, ...)
30406        self.write_keyword("INHERITS");
30407        self.write(" (");
30408        for (i, expr) in e.expressions.iter().enumerate() {
30409            if i > 0 {
30410                self.write(", ");
30411            }
30412            self.generate_expression(expr)?;
30413        }
30414        self.write(")");
30415        Ok(())
30416    }
30417
30418    fn generate_input_model_property(&mut self, e: &InputModelProperty) -> Result<()> {
30419        // INPUT(model)
30420        self.write_keyword("INPUT");
30421        self.write("(");
30422        self.generate_expression(&e.this)?;
30423        self.write(")");
30424        Ok(())
30425    }
30426
30427    fn generate_input_output_format(&mut self, e: &InputOutputFormat) -> Result<()> {
30428        // Python: INPUTFORMAT input_format OUTPUTFORMAT output_format
30429        if let Some(input_format) = &e.input_format {
30430            self.write_keyword("INPUTFORMAT");
30431            self.write_space();
30432            self.generate_expression(input_format)?;
30433        }
30434        if let Some(output_format) = &e.output_format {
30435            if e.input_format.is_some() {
30436                self.write(" ");
30437            }
30438            self.write_keyword("OUTPUTFORMAT");
30439            self.write_space();
30440            self.generate_expression(output_format)?;
30441        }
30442        Ok(())
30443    }
30444
30445    fn generate_install(&mut self, e: &Install) -> Result<()> {
30446        // [FORCE] INSTALL extension [FROM source]
30447        if e.force.is_some() {
30448            self.write_keyword("FORCE");
30449            self.write_space();
30450        }
30451        self.write_keyword("INSTALL");
30452        self.write_space();
30453        self.generate_expression(&e.this)?;
30454        if let Some(from) = &e.from_ {
30455            self.write_space();
30456            self.write_keyword("FROM");
30457            self.write_space();
30458            self.generate_expression(from)?;
30459        }
30460        Ok(())
30461    }
30462
30463    fn generate_interval_op(&mut self, e: &IntervalOp) -> Result<()> {
30464        // INTERVAL 'expression' unit
30465        self.write_keyword("INTERVAL");
30466        self.write_space();
30467        // When a unit is specified and the expression is a number,
30468        self.generate_expression(&e.expression)?;
30469        if let Some(unit) = &e.unit {
30470            self.write_space();
30471            self.write(unit);
30472        }
30473        Ok(())
30474    }
30475
30476    fn generate_interval_span(&mut self, e: &IntervalSpan) -> Result<()> {
30477        // unit TO unit (e.g., HOUR TO SECOND)
30478        self.write(&format!("{:?}", e.this).to_ascii_uppercase());
30479        self.write_space();
30480        self.write_keyword("TO");
30481        self.write_space();
30482        self.write(&format!("{:?}", e.expression).to_ascii_uppercase());
30483        Ok(())
30484    }
30485
30486    fn generate_into_clause(&mut self, e: &IntoClause) -> Result<()> {
30487        // INTO [TEMPORARY|UNLOGGED] table
30488        self.write_keyword("INTO");
30489        if e.temporary {
30490            self.write_keyword(" TEMPORARY");
30491        }
30492        if e.unlogged.is_some() {
30493            self.write_keyword(" UNLOGGED");
30494        }
30495        if let Some(this) = &e.this {
30496            self.write_space();
30497            self.generate_expression(this)?;
30498        }
30499        if !e.expressions.is_empty() {
30500            self.write(" (");
30501            for (i, expr) in e.expressions.iter().enumerate() {
30502                if i > 0 {
30503                    self.write(", ");
30504                }
30505                self.generate_expression(expr)?;
30506            }
30507            self.write(")");
30508        }
30509        Ok(())
30510    }
30511
30512    fn generate_introducer(&mut self, e: &Introducer) -> Result<()> {
30513        // Python: this expression (e.g., _utf8 'string')
30514        self.generate_expression(&e.this)?;
30515        self.write_space();
30516        self.generate_expression(&e.expression)?;
30517        Ok(())
30518    }
30519
30520    fn generate_isolated_loading_property(&mut self, e: &IsolatedLoadingProperty) -> Result<()> {
30521        // Python: WITH [NO] [CONCURRENT] ISOLATED LOADING [target]
30522        self.write_keyword("WITH");
30523        if e.no.is_some() {
30524            self.write_keyword(" NO");
30525        }
30526        if e.concurrent.is_some() {
30527            self.write_keyword(" CONCURRENT");
30528        }
30529        self.write_keyword(" ISOLATED LOADING");
30530        if let Some(target) = &e.target {
30531            self.write_space();
30532            self.generate_expression(target)?;
30533        }
30534        Ok(())
30535    }
30536
30537    fn generate_json(&mut self, e: &JSON) -> Result<()> {
30538        // Python: JSON [this] [WITHOUT|WITH] [UNIQUE KEYS]
30539        self.write_keyword("JSON");
30540        if let Some(this) = &e.this {
30541            self.write_space();
30542            self.generate_expression(this)?;
30543        }
30544        if let Some(with_) = &e.with_ {
30545            // Check if it's a truthy boolean
30546            if let Expression::Boolean(b) = with_.as_ref() {
30547                if b.value {
30548                    self.write_keyword(" WITH");
30549                } else {
30550                    self.write_keyword(" WITHOUT");
30551                }
30552            }
30553        }
30554        if e.unique {
30555            self.write_keyword(" UNIQUE KEYS");
30556        }
30557        Ok(())
30558    }
30559
30560    fn generate_json_array(&mut self, e: &JSONArray) -> Result<()> {
30561        // Python: return self.func("JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})")
30562        self.write_keyword("JSON_ARRAY");
30563        self.write("(");
30564        for (i, expr) in e.expressions.iter().enumerate() {
30565            if i > 0 {
30566                self.write(", ");
30567            }
30568            self.generate_expression(expr)?;
30569        }
30570        if let Some(null_handling) = &e.null_handling {
30571            self.write_space();
30572            self.generate_expression(null_handling)?;
30573        }
30574        if let Some(return_type) = &e.return_type {
30575            self.write_space();
30576            self.write_keyword("RETURNING");
30577            self.write_space();
30578            self.generate_expression(return_type)?;
30579        }
30580        if e.strict.is_some() {
30581            self.write_space();
30582            self.write_keyword("STRICT");
30583        }
30584        self.write(")");
30585        Ok(())
30586    }
30587
30588    fn generate_json_array_agg_struct(&mut self, e: &JSONArrayAgg) -> Result<()> {
30589        // JSON_ARRAYAGG(this [ORDER BY ...] [NULL ON NULL | ABSENT ON NULL] [RETURNING type] [STRICT])
30590        self.write_keyword("JSON_ARRAYAGG");
30591        self.write("(");
30592        self.generate_expression(&e.this)?;
30593        if let Some(order) = &e.order {
30594            self.write_space();
30595            // Order is stored as an OrderBy expression
30596            if let Expression::OrderBy(ob) = order.as_ref() {
30597                self.write_keyword("ORDER BY");
30598                self.write_space();
30599                for (i, ord) in ob.expressions.iter().enumerate() {
30600                    if i > 0 {
30601                        self.write(", ");
30602                    }
30603                    self.generate_ordered(ord)?;
30604                }
30605            } else {
30606                // Fallback: generate the expression directly
30607                self.generate_expression(order)?;
30608            }
30609        }
30610        if let Some(null_handling) = &e.null_handling {
30611            self.write_space();
30612            self.generate_expression(null_handling)?;
30613        }
30614        if let Some(return_type) = &e.return_type {
30615            self.write_space();
30616            self.write_keyword("RETURNING");
30617            self.write_space();
30618            self.generate_expression(return_type)?;
30619        }
30620        if e.strict.is_some() {
30621            self.write_space();
30622            self.write_keyword("STRICT");
30623        }
30624        self.write(")");
30625        Ok(())
30626    }
30627
30628    fn generate_json_object_agg_struct(&mut self, e: &JSONObjectAgg) -> Result<()> {
30629        // JSON_OBJECTAGG(key: value [NULL ON NULL | ABSENT ON NULL] [WITH UNIQUE KEYS] [RETURNING type])
30630        self.write_keyword("JSON_OBJECTAGG");
30631        self.write("(");
30632        for (i, expr) in e.expressions.iter().enumerate() {
30633            if i > 0 {
30634                self.write(", ");
30635            }
30636            self.generate_expression(expr)?;
30637        }
30638        if let Some(null_handling) = &e.null_handling {
30639            self.write_space();
30640            self.generate_expression(null_handling)?;
30641        }
30642        if let Some(unique_keys) = &e.unique_keys {
30643            self.write_space();
30644            if let Expression::Boolean(b) = unique_keys.as_ref() {
30645                if b.value {
30646                    self.write_keyword("WITH UNIQUE KEYS");
30647                } else {
30648                    self.write_keyword("WITHOUT UNIQUE KEYS");
30649                }
30650            }
30651        }
30652        if let Some(return_type) = &e.return_type {
30653            self.write_space();
30654            self.write_keyword("RETURNING");
30655            self.write_space();
30656            self.generate_expression(return_type)?;
30657        }
30658        self.write(")");
30659        Ok(())
30660    }
30661
30662    fn generate_json_array_append(&mut self, e: &JSONArrayAppend) -> Result<()> {
30663        // JSON_ARRAY_APPEND(this, path, value, ...)
30664        self.write_keyword("JSON_ARRAY_APPEND");
30665        self.write("(");
30666        self.generate_expression(&e.this)?;
30667        for expr in &e.expressions {
30668            self.write(", ");
30669            self.generate_expression(expr)?;
30670        }
30671        self.write(")");
30672        Ok(())
30673    }
30674
30675    fn generate_json_array_contains(&mut self, e: &JSONArrayContains) -> Result<()> {
30676        // JSON_ARRAY_CONTAINS(this, expression)
30677        self.write_keyword("JSON_ARRAY_CONTAINS");
30678        self.write("(");
30679        self.generate_expression(&e.this)?;
30680        self.write(", ");
30681        self.generate_expression(&e.expression)?;
30682        self.write(")");
30683        Ok(())
30684    }
30685
30686    fn generate_json_array_insert(&mut self, e: &JSONArrayInsert) -> Result<()> {
30687        // JSON_ARRAY_INSERT(this, path, value, ...)
30688        self.write_keyword("JSON_ARRAY_INSERT");
30689        self.write("(");
30690        self.generate_expression(&e.this)?;
30691        for expr in &e.expressions {
30692            self.write(", ");
30693            self.generate_expression(expr)?;
30694        }
30695        self.write(")");
30696        Ok(())
30697    }
30698
30699    fn generate_jsonb_exists(&mut self, e: &JSONBExists) -> Result<()> {
30700        // JSONB_EXISTS(this, path)
30701        self.write_keyword("JSONB_EXISTS");
30702        self.write("(");
30703        self.generate_expression(&e.this)?;
30704        if let Some(path) = &e.path {
30705            self.write(", ");
30706            self.generate_expression(path)?;
30707        }
30708        self.write(")");
30709        Ok(())
30710    }
30711
30712    fn generate_jsonb_extract_scalar(&mut self, e: &JSONBExtractScalar) -> Result<()> {
30713        // JSONB_EXTRACT_SCALAR(this, expression)
30714        self.write_keyword("JSONB_EXTRACT_SCALAR");
30715        self.write("(");
30716        self.generate_expression(&e.this)?;
30717        self.write(", ");
30718        self.generate_expression(&e.expression)?;
30719        self.write(")");
30720        Ok(())
30721    }
30722
30723    fn generate_jsonb_object_agg(&mut self, e: &JSONBObjectAgg) -> Result<()> {
30724        // JSONB_OBJECT_AGG(this, expression)
30725        self.write_keyword("JSONB_OBJECT_AGG");
30726        self.write("(");
30727        self.generate_expression(&e.this)?;
30728        self.write(", ");
30729        self.generate_expression(&e.expression)?;
30730        self.write(")");
30731        Ok(())
30732    }
30733
30734    fn generate_json_column_def(&mut self, e: &JSONColumnDef) -> Result<()> {
30735        // Python: NESTED PATH path schema | this kind PATH path [FOR ORDINALITY]
30736        if let Some(nested_schema) = &e.nested_schema {
30737            self.write_keyword("NESTED");
30738            if let Some(path) = &e.path {
30739                self.write_space();
30740                self.write_keyword("PATH");
30741                self.write_space();
30742                self.generate_expression(path)?;
30743            }
30744            self.write_space();
30745            self.generate_expression(nested_schema)?;
30746        } else {
30747            if let Some(this) = &e.this {
30748                self.generate_expression(this)?;
30749            }
30750            if let Some(kind) = &e.kind {
30751                self.write_space();
30752                self.write(kind);
30753            }
30754            if e.format_json {
30755                self.write_space();
30756                self.write_keyword("FORMAT JSON");
30757            }
30758            if let Some(path) = &e.path {
30759                self.write_space();
30760                self.write_keyword("PATH");
30761                self.write_space();
30762                self.generate_expression(path)?;
30763            }
30764            if e.ordinality.is_some() {
30765                self.write_keyword(" FOR ORDINALITY");
30766            }
30767        }
30768        Ok(())
30769    }
30770
30771    fn generate_json_exists(&mut self, e: &JSONExists) -> Result<()> {
30772        // JSON_EXISTS(this, path PASSING vars ON ERROR/EMPTY condition)
30773        self.write_keyword("JSON_EXISTS");
30774        self.write("(");
30775        self.generate_expression(&e.this)?;
30776        if let Some(path) = &e.path {
30777            self.write(", ");
30778            self.generate_expression(path)?;
30779        }
30780        if let Some(passing) = &e.passing {
30781            self.write_space();
30782            self.write_keyword("PASSING");
30783            self.write_space();
30784            self.generate_expression(passing)?;
30785        }
30786        if let Some(on_condition) = &e.on_condition {
30787            self.write_space();
30788            self.generate_expression(on_condition)?;
30789        }
30790        self.write(")");
30791        Ok(())
30792    }
30793
30794    fn generate_json_cast(&mut self, e: &JSONCast) -> Result<()> {
30795        self.generate_expression(&e.this)?;
30796        self.write(".:");
30797        // If the data type has nested type parameters (like Array(JSON), Map(String, Int)),
30798        // wrap the entire type string in double quotes.
30799        // This matches Python sqlglot's ClickHouse _json_cast_sql behavior.
30800        if Self::data_type_has_nested_expressions(&e.to) {
30801            // Generate the data type to a temporary string buffer, then wrap in quotes
30802            let saved = std::mem::take(&mut self.output);
30803            self.generate_data_type(&e.to)?;
30804            let type_sql = std::mem::replace(&mut self.output, saved);
30805            self.write("\"");
30806            self.write(&type_sql);
30807            self.write("\"");
30808        } else {
30809            self.generate_data_type(&e.to)?;
30810        }
30811        Ok(())
30812    }
30813
30814    /// Check if a DataType has nested type expressions (sub-types).
30815    /// This corresponds to Python sqlglot's `to.expressions` being non-empty.
30816    fn data_type_has_nested_expressions(dt: &DataType) -> bool {
30817        matches!(
30818            dt,
30819            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
30820        )
30821    }
30822
30823    fn generate_json_extract_array(&mut self, e: &JSONExtractArray) -> Result<()> {
30824        // JSON_EXTRACT_ARRAY(this, expression)
30825        self.write_keyword("JSON_EXTRACT_ARRAY");
30826        self.write("(");
30827        self.generate_expression(&e.this)?;
30828        if let Some(expr) = &e.expression {
30829            self.write(", ");
30830            self.generate_expression(expr)?;
30831        }
30832        self.write(")");
30833        Ok(())
30834    }
30835
30836    fn generate_json_extract_quote(&mut self, e: &JSONExtractQuote) -> Result<()> {
30837        // Snowflake: KEEP [OMIT] QUOTES [SCALAR_ONLY] for JSON extraction
30838        if let Some(option) = &e.option {
30839            self.generate_expression(option)?;
30840            self.write_space();
30841        }
30842        self.write_keyword("QUOTES");
30843        if e.scalar.is_some() {
30844            self.write_keyword(" SCALAR_ONLY");
30845        }
30846        Ok(())
30847    }
30848
30849    fn generate_json_extract_scalar(&mut self, e: &JSONExtractScalar) -> Result<()> {
30850        // JSON_EXTRACT_SCALAR(this, expression)
30851        self.write_keyword("JSON_EXTRACT_SCALAR");
30852        self.write("(");
30853        self.generate_expression(&e.this)?;
30854        self.write(", ");
30855        self.generate_expression(&e.expression)?;
30856        self.write(")");
30857        Ok(())
30858    }
30859
30860    fn generate_json_extract_path(&mut self, e: &JSONExtract) -> Result<()> {
30861        // For variant_extract (Snowflake/Databricks colon syntax like a:field)
30862        // Databricks uses col:path syntax, Snowflake uses GET_PATH(col, 'path')
30863        // Otherwise output JSON_EXTRACT(this, expression)
30864        if e.variant_extract.is_some() {
30865            use crate::dialects::DialectType;
30866            if matches!(self.config.dialect, Some(DialectType::Databricks)) {
30867                // Databricks: output col:path syntax (e.g., c1:price, c1:price.foo, c1:price.bar[1])
30868                // Keys that are not safe identifiers (contain hyphens, spaces, etc.) must use
30869                // bracket notation: c:["x-y"] instead of c:x-y
30870                self.generate_expression(&e.this)?;
30871                self.write(":");
30872                match e.expression.as_ref() {
30873                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
30874                        let Literal::String(s) = lit.as_ref() else {
30875                            unreachable!()
30876                        };
30877                        self.write_databricks_json_path(s);
30878                    }
30879                    _ => {
30880                        // Fallback: generate as-is (shouldn't happen in typical cases)
30881                        self.generate_expression(&e.expression)?;
30882                    }
30883                }
30884            } else {
30885                // Snowflake and others: use GET_PATH(col, 'path')
30886                self.write_keyword("GET_PATH");
30887                self.write("(");
30888                self.generate_expression(&e.this)?;
30889                self.write(", ");
30890                self.generate_expression(&e.expression)?;
30891                self.write(")");
30892            }
30893        } else {
30894            self.write_keyword("JSON_EXTRACT");
30895            self.write("(");
30896            self.generate_expression(&e.this)?;
30897            self.write(", ");
30898            self.generate_expression(&e.expression)?;
30899            for expr in &e.expressions {
30900                self.write(", ");
30901                self.generate_expression(expr)?;
30902            }
30903            self.write(")");
30904        }
30905        Ok(())
30906    }
30907
30908    /// Write a Databricks JSON colon-path, using bracket notation for keys
30909    /// that are not safe identifiers (e.g., contain hyphens, spaces, etc.)
30910    /// Safe identifier regex: ^[_a-zA-Z]\w*$
30911    fn write_databricks_json_path(&mut self, path: &str) {
30912        // If the path already starts with bracket notation (e.g., '["fr\'uit"]'),
30913        // it was already formatted by the parser - output as-is
30914        if path.starts_with("[\"") || path.starts_with("['") {
30915            self.write(path);
30916            return;
30917        }
30918        // Split the path into segments at '.' boundaries, but preserve bracket subscripts
30919        // e.g., "price.items[0].name" -> ["price", "items[0]", "name"]
30920        // e.g., "x-y" -> ["x-y"]
30921        let mut first = true;
30922        for segment in path.split('.') {
30923            if !first {
30924                self.write(".");
30925            }
30926            first = false;
30927            // Check if there's a bracket subscript in this segment: "items[0]"
30928            if let Some(bracket_pos) = segment.find('[') {
30929                let key = &segment[..bracket_pos];
30930                let subscript = &segment[bracket_pos..];
30931                if key.is_empty() {
30932                    // Bracket notation at start of segment (e.g., already formatted)
30933                    self.write(segment);
30934                } else if Self::is_safe_json_path_key(key) {
30935                    self.write(key);
30936                    self.write(subscript);
30937                } else {
30938                    self.write("[\"");
30939                    self.write(key);
30940                    self.write("\"]");
30941                    self.write(subscript);
30942                }
30943            } else if Self::is_safe_json_path_key(segment) {
30944                self.write(segment);
30945            } else {
30946                self.write("[\"");
30947                self.write(segment);
30948                self.write("\"]");
30949            }
30950        }
30951    }
30952
30953    /// Check if a JSON path key is a safe identifier that doesn't need bracket quoting.
30954    /// Matches Python sqlglot's SAFE_IDENTIFIER_RE: ^[_a-zA-Z]\w*$
30955    fn is_safe_json_path_key(key: &str) -> bool {
30956        if key.is_empty() {
30957            return false;
30958        }
30959        let mut chars = key.chars();
30960        let first = chars.next().unwrap();
30961        if first != '_' && !first.is_ascii_alphabetic() {
30962            return false;
30963        }
30964        chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
30965    }
30966
30967    fn generate_json_format(&mut self, e: &JSONFormat) -> Result<()> {
30968        // Output: {expr} FORMAT JSON
30969        // This wraps an expression with FORMAT JSON suffix (Oracle JSON function syntax)
30970        if let Some(this) = &e.this {
30971            self.generate_expression(this)?;
30972            self.write_space();
30973        }
30974        self.write_keyword("FORMAT JSON");
30975        Ok(())
30976    }
30977
30978    fn generate_json_key_value(&mut self, e: &JSONKeyValue) -> Result<()> {
30979        // key: value (for JSON objects)
30980        self.generate_expression(&e.this)?;
30981        self.write(": ");
30982        self.generate_expression(&e.expression)?;
30983        Ok(())
30984    }
30985
30986    fn generate_json_keys(&mut self, e: &JSONKeys) -> Result<()> {
30987        // JSON_KEYS(this, expression, expressions...)
30988        self.write_keyword("JSON_KEYS");
30989        self.write("(");
30990        self.generate_expression(&e.this)?;
30991        if let Some(expr) = &e.expression {
30992            self.write(", ");
30993            self.generate_expression(expr)?;
30994        }
30995        for expr in &e.expressions {
30996            self.write(", ");
30997            self.generate_expression(expr)?;
30998        }
30999        self.write(")");
31000        Ok(())
31001    }
31002
31003    fn generate_json_keys_at_depth(&mut self, e: &JSONKeysAtDepth) -> Result<()> {
31004        // JSON_KEYS(this, expression)
31005        self.write_keyword("JSON_KEYS");
31006        self.write("(");
31007        self.generate_expression(&e.this)?;
31008        if let Some(expr) = &e.expression {
31009            self.write(", ");
31010            self.generate_expression(expr)?;
31011        }
31012        self.write(")");
31013        Ok(())
31014    }
31015
31016    fn generate_json_path_expr(&mut self, e: &JSONPath) -> Result<()> {
31017        // JSONPath expression: generates a quoted path like '$.foo' or '$[0]'
31018        // The path components are concatenated without spaces
31019        let mut path_str = String::new();
31020        for expr in &e.expressions {
31021            match expr {
31022                Expression::JSONPathRoot(_) => {
31023                    path_str.push('$');
31024                }
31025                Expression::JSONPathKey(k) => {
31026                    // .key or ."key" (quote if key has special characters)
31027                    if let Expression::Literal(lit) = k.this.as_ref() {
31028                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
31029                            path_str.push('.');
31030                            // Quote the key if it contains non-alphanumeric characters (hyphens, spaces, etc.)
31031                            let needs_quoting = s.chars().any(|c| !c.is_alphanumeric() && c != '_');
31032                            if needs_quoting {
31033                                path_str.push('"');
31034                                path_str.push_str(s);
31035                                path_str.push('"');
31036                            } else {
31037                                path_str.push_str(s);
31038                            }
31039                        }
31040                    }
31041                }
31042                Expression::JSONPathSubscript(s) => {
31043                    // [index]
31044                    if let Expression::Literal(lit) = s.this.as_ref() {
31045                        if let crate::expressions::Literal::Number(n) = lit.as_ref() {
31046                            path_str.push('[');
31047                            path_str.push_str(n);
31048                            path_str.push(']');
31049                        }
31050                    }
31051                }
31052                _ => {
31053                    // For other path parts, try to generate them
31054                    let mut temp_gen = Self::with_arc_config(self.config.clone());
31055                    temp_gen.generate_expression(expr)?;
31056                    path_str.push_str(&temp_gen.output);
31057                }
31058            }
31059        }
31060        // Output as quoted string
31061        self.write("'");
31062        self.write(&path_str);
31063        self.write("'");
31064        Ok(())
31065    }
31066
31067    fn generate_json_path_filter(&mut self, e: &JSONPathFilter) -> Result<()> {
31068        // JSON path filter: ?(predicate)
31069        self.write("?(");
31070        self.generate_expression(&e.this)?;
31071        self.write(")");
31072        Ok(())
31073    }
31074
31075    fn generate_json_path_key(&mut self, e: &JSONPathKey) -> Result<()> {
31076        // JSON path key: .key or ["key"]
31077        self.write(".");
31078        self.generate_expression(&e.this)?;
31079        Ok(())
31080    }
31081
31082    fn generate_json_path_recursive(&mut self, e: &JSONPathRecursive) -> Result<()> {
31083        // JSON path recursive descent: ..
31084        self.write("..");
31085        if let Some(this) = &e.this {
31086            self.generate_expression(this)?;
31087        }
31088        Ok(())
31089    }
31090
31091    fn generate_json_path_root(&mut self) -> Result<()> {
31092        // JSON path root: $
31093        self.write("$");
31094        Ok(())
31095    }
31096
31097    fn generate_json_path_script(&mut self, e: &JSONPathScript) -> Result<()> {
31098        // JSON path script: (expression)
31099        self.write("(");
31100        self.generate_expression(&e.this)?;
31101        self.write(")");
31102        Ok(())
31103    }
31104
31105    fn generate_json_path_selector(&mut self, e: &JSONPathSelector) -> Result<()> {
31106        // JSON path selector: *
31107        self.generate_expression(&e.this)?;
31108        Ok(())
31109    }
31110
31111    fn generate_json_path_slice(&mut self, e: &JSONPathSlice) -> Result<()> {
31112        // JSON path slice: [start:end:step]
31113        self.write("[");
31114        if let Some(start) = &e.start {
31115            self.generate_expression(start)?;
31116        }
31117        self.write(":");
31118        if let Some(end) = &e.end {
31119            self.generate_expression(end)?;
31120        }
31121        if let Some(step) = &e.step {
31122            self.write(":");
31123            self.generate_expression(step)?;
31124        }
31125        self.write("]");
31126        Ok(())
31127    }
31128
31129    fn generate_json_path_subscript(&mut self, e: &JSONPathSubscript) -> Result<()> {
31130        // JSON path subscript: [index] or [*]
31131        self.write("[");
31132        self.generate_expression(&e.this)?;
31133        self.write("]");
31134        Ok(())
31135    }
31136
31137    fn generate_json_path_union(&mut self, e: &JSONPathUnion) -> Result<()> {
31138        // JSON path union: [key1, key2, ...]
31139        self.write("[");
31140        for (i, expr) in e.expressions.iter().enumerate() {
31141            if i > 0 {
31142                self.write(", ");
31143            }
31144            self.generate_expression(expr)?;
31145        }
31146        self.write("]");
31147        Ok(())
31148    }
31149
31150    fn generate_json_remove(&mut self, e: &JSONRemove) -> Result<()> {
31151        // JSON_REMOVE(this, path1, path2, ...)
31152        self.write_keyword("JSON_REMOVE");
31153        self.write("(");
31154        self.generate_expression(&e.this)?;
31155        for expr in &e.expressions {
31156            self.write(", ");
31157            self.generate_expression(expr)?;
31158        }
31159        self.write(")");
31160        Ok(())
31161    }
31162
31163    fn generate_json_schema(&mut self, e: &JSONSchema) -> Result<()> {
31164        // COLUMNS(col1 type, col2 type, ...)
31165        // When pretty printing and content is too wide, format with each column on a separate line
31166        self.write_keyword("COLUMNS");
31167        self.write("(");
31168
31169        if self.config.pretty && !e.expressions.is_empty() {
31170            // First, generate all expressions into strings to check width
31171            let mut expr_strings: Vec<String> = Vec::with_capacity(e.expressions.len());
31172            for expr in &e.expressions {
31173                let mut temp_gen = Generator::with_arc_config(self.config.clone());
31174                temp_gen.generate_expression(expr)?;
31175                expr_strings.push(temp_gen.output);
31176            }
31177
31178            // Check if total width exceeds max_text_width
31179            if self.too_wide(&expr_strings) {
31180                // Pretty print: each column on its own line
31181                self.write_newline();
31182                self.indent_level += 1;
31183                for (i, expr_str) in expr_strings.iter().enumerate() {
31184                    if i > 0 {
31185                        self.write(",");
31186                        self.write_newline();
31187                    }
31188                    self.write_indent();
31189                    self.write(expr_str);
31190                }
31191                self.write_newline();
31192                self.indent_level -= 1;
31193                self.write_indent();
31194            } else {
31195                // Compact: all on one line
31196                for (i, expr_str) in expr_strings.iter().enumerate() {
31197                    if i > 0 {
31198                        self.write(", ");
31199                    }
31200                    self.write(expr_str);
31201                }
31202            }
31203        } else {
31204            // Non-pretty mode: compact format
31205            for (i, expr) in e.expressions.iter().enumerate() {
31206                if i > 0 {
31207                    self.write(", ");
31208                }
31209                self.generate_expression(expr)?;
31210            }
31211        }
31212        self.write(")");
31213        Ok(())
31214    }
31215
31216    fn generate_json_set(&mut self, e: &JSONSet) -> Result<()> {
31217        // JSON_SET(this, path, value, ...)
31218        self.write_keyword("JSON_SET");
31219        self.write("(");
31220        self.generate_expression(&e.this)?;
31221        for expr in &e.expressions {
31222            self.write(", ");
31223            self.generate_expression(expr)?;
31224        }
31225        self.write(")");
31226        Ok(())
31227    }
31228
31229    fn generate_json_strip_nulls(&mut self, e: &JSONStripNulls) -> Result<()> {
31230        // JSON_STRIP_NULLS(this, expression)
31231        self.write_keyword("JSON_STRIP_NULLS");
31232        self.write("(");
31233        self.generate_expression(&e.this)?;
31234        if let Some(expr) = &e.expression {
31235            self.write(", ");
31236            self.generate_expression(expr)?;
31237        }
31238        self.write(")");
31239        Ok(())
31240    }
31241
31242    fn generate_json_table(&mut self, e: &JSONTable) -> Result<()> {
31243        // JSON_TABLE(this, path [error_handling] [empty_handling] schema)
31244        self.write_keyword("JSON_TABLE");
31245        self.write("(");
31246        self.generate_expression(&e.this)?;
31247        if let Some(path) = &e.path {
31248            self.write(", ");
31249            self.generate_expression(path)?;
31250        }
31251        if let Some(error_handling) = &e.error_handling {
31252            self.write_space();
31253            self.generate_expression(error_handling)?;
31254        }
31255        if let Some(empty_handling) = &e.empty_handling {
31256            self.write_space();
31257            self.generate_expression(empty_handling)?;
31258        }
31259        if let Some(schema) = &e.schema {
31260            self.write_space();
31261            self.generate_expression(schema)?;
31262        }
31263        self.write(")");
31264        Ok(())
31265    }
31266
31267    fn generate_json_type(&mut self, e: &JSONType) -> Result<()> {
31268        // JSON_TYPE(this)
31269        self.write_keyword("JSON_TYPE");
31270        self.write("(");
31271        self.generate_expression(&e.this)?;
31272        self.write(")");
31273        Ok(())
31274    }
31275
31276    fn generate_json_value(&mut self, e: &JSONValue) -> Result<()> {
31277        // JSON_VALUE(this, path RETURNING type ON condition)
31278        self.write_keyword("JSON_VALUE");
31279        self.write("(");
31280        self.generate_expression(&e.this)?;
31281        if let Some(path) = &e.path {
31282            self.write(", ");
31283            self.generate_expression(path)?;
31284        }
31285        if let Some(returning) = &e.returning {
31286            self.write_space();
31287            self.write_keyword("RETURNING");
31288            self.write_space();
31289            self.generate_expression(returning)?;
31290        }
31291        if let Some(on_condition) = &e.on_condition {
31292            self.write_space();
31293            self.generate_expression(on_condition)?;
31294        }
31295        self.write(")");
31296        Ok(())
31297    }
31298
31299    fn generate_json_value_array(&mut self, e: &JSONValueArray) -> Result<()> {
31300        // JSON_VALUE_ARRAY(this)
31301        self.write_keyword("JSON_VALUE_ARRAY");
31302        self.write("(");
31303        self.generate_expression(&e.this)?;
31304        self.write(")");
31305        Ok(())
31306    }
31307
31308    fn generate_jarowinkler_similarity(&mut self, e: &JarowinklerSimilarity) -> Result<()> {
31309        // JAROWINKLER_SIMILARITY(str1, str2)
31310        self.write_keyword("JAROWINKLER_SIMILARITY");
31311        self.write("(");
31312        self.generate_expression(&e.this)?;
31313        self.write(", ");
31314        self.generate_expression(&e.expression)?;
31315        self.write(")");
31316        Ok(())
31317    }
31318
31319    fn generate_join_hint(&mut self, e: &JoinHint) -> Result<()> {
31320        // Python: this(expressions)
31321        self.generate_expression(&e.this)?;
31322        self.write("(");
31323        for (i, expr) in e.expressions.iter().enumerate() {
31324            if i > 0 {
31325                self.write(", ");
31326            }
31327            self.generate_expression(expr)?;
31328        }
31329        self.write(")");
31330        Ok(())
31331    }
31332
31333    fn generate_journal_property(&mut self, e: &JournalProperty) -> Result<()> {
31334        // Python: {no}{local}{dual}{before}{after}JOURNAL
31335        if e.no.is_some() {
31336            self.write_keyword("NO ");
31337        }
31338        if let Some(local) = &e.local {
31339            self.generate_expression(local)?;
31340            self.write_space();
31341        }
31342        if e.dual.is_some() {
31343            self.write_keyword("DUAL ");
31344        }
31345        if e.before.is_some() {
31346            self.write_keyword("BEFORE ");
31347        }
31348        if e.after.is_some() {
31349            self.write_keyword("AFTER ");
31350        }
31351        self.write_keyword("JOURNAL");
31352        Ok(())
31353    }
31354
31355    fn generate_language_property(&mut self, e: &LanguageProperty) -> Result<()> {
31356        // LANGUAGE language_name
31357        self.write_keyword("LANGUAGE");
31358        self.write_space();
31359        self.generate_expression(&e.this)?;
31360        Ok(())
31361    }
31362
31363    fn generate_lateral(&mut self, e: &Lateral) -> Result<()> {
31364        // Python: handles LATERAL VIEW (Hive/Spark) and regular LATERAL
31365        if e.view.is_some() {
31366            // LATERAL VIEW [OUTER] expression [alias] [AS columns]
31367            self.write_keyword("LATERAL VIEW");
31368            if e.outer.is_some() {
31369                self.write_space();
31370                self.write_keyword("OUTER");
31371            }
31372            self.write_space();
31373            self.generate_expression(&e.this)?;
31374            if let Some(alias) = &e.alias {
31375                self.write_space();
31376                self.write(alias);
31377            }
31378        } else {
31379            // LATERAL subquery/function [WITH ORDINALITY] [AS alias(columns)]
31380            self.write_keyword("LATERAL");
31381            self.write_space();
31382            self.generate_expression(&e.this)?;
31383            if e.ordinality.is_some() {
31384                self.write_space();
31385                self.write_keyword("WITH ORDINALITY");
31386            }
31387            if let Some(alias) = &e.alias {
31388                self.write_space();
31389                self.write_keyword("AS");
31390                self.write_space();
31391                self.write(alias);
31392                if !e.column_aliases.is_empty() {
31393                    self.write("(");
31394                    for (i, col) in e.column_aliases.iter().enumerate() {
31395                        if i > 0 {
31396                            self.write(", ");
31397                        }
31398                        self.write(col);
31399                    }
31400                    self.write(")");
31401                }
31402            }
31403        }
31404        Ok(())
31405    }
31406
31407    fn generate_like_property(&mut self, e: &LikeProperty) -> Result<()> {
31408        // Python: LIKE this [options]
31409        self.write_keyword("LIKE");
31410        self.write_space();
31411        self.generate_expression(&e.this)?;
31412        for expr in &e.expressions {
31413            self.write_space();
31414            self.generate_expression(expr)?;
31415        }
31416        Ok(())
31417    }
31418
31419    fn generate_limit(&mut self, e: &Limit) -> Result<()> {
31420        self.write_keyword("LIMIT");
31421        self.write_space();
31422        self.write_limit_expr(&e.this)?;
31423        if e.percent {
31424            self.write_space();
31425            self.write_keyword("PERCENT");
31426        }
31427        // Emit any comments that were captured from before the LIMIT keyword
31428        for comment in &e.comments {
31429            self.write(" ");
31430            self.write_formatted_comment(comment);
31431        }
31432        Ok(())
31433    }
31434
31435    fn generate_limit_options(&mut self, e: &LimitOptions) -> Result<()> {
31436        // Python: [PERCENT][ROWS][WITH TIES|ONLY]
31437        if e.percent.is_some() {
31438            self.write_keyword(" PERCENT");
31439        }
31440        if e.rows.is_some() {
31441            self.write_keyword(" ROWS");
31442        }
31443        if e.with_ties.is_some() {
31444            self.write_keyword(" WITH TIES");
31445        } else if e.rows.is_some() {
31446            self.write_keyword(" ONLY");
31447        }
31448        Ok(())
31449    }
31450
31451    fn generate_list(&mut self, e: &List) -> Result<()> {
31452        use crate::dialects::DialectType;
31453        let is_materialize = matches!(self.config.dialect, Some(DialectType::Materialize));
31454
31455        // Check if this is a subquery-based list (LIST(SELECT ...))
31456        if e.expressions.len() == 1 {
31457            if let Expression::Select(_) = &e.expressions[0] {
31458                self.write_keyword("LIST");
31459                self.write("(");
31460                self.generate_expression(&e.expressions[0])?;
31461                self.write(")");
31462                return Ok(());
31463            }
31464        }
31465
31466        // For Materialize, output as LIST[expr, expr, ...]
31467        if is_materialize {
31468            self.write_keyword("LIST");
31469            self.write("[");
31470            for (i, expr) in e.expressions.iter().enumerate() {
31471                if i > 0 {
31472                    self.write(", ");
31473                }
31474                self.generate_expression(expr)?;
31475            }
31476            self.write("]");
31477        } else {
31478            // For other dialects, output as LIST(expr, expr, ...)
31479            self.write_keyword("LIST");
31480            self.write("(");
31481            for (i, expr) in e.expressions.iter().enumerate() {
31482                if i > 0 {
31483                    self.write(", ");
31484                }
31485                self.generate_expression(expr)?;
31486            }
31487            self.write(")");
31488        }
31489        Ok(())
31490    }
31491
31492    fn generate_tomap(&mut self, e: &ToMap) -> Result<()> {
31493        // Check if this is a subquery-based map (MAP(SELECT ...))
31494        if let Expression::Select(_) = &*e.this {
31495            self.write_keyword("MAP");
31496            self.write("(");
31497            self.generate_expression(&e.this)?;
31498            self.write(")");
31499            return Ok(());
31500        }
31501
31502        let is_duckdb = matches!(self.config.dialect, Some(DialectType::DuckDB));
31503
31504        // For Struct-based map: DuckDB uses MAP {'key': value}, Materialize uses MAP['key' => value]
31505        self.write_keyword("MAP");
31506        if is_duckdb {
31507            self.write(" {");
31508        } else {
31509            self.write("[");
31510        }
31511        if let Expression::Struct(s) = &*e.this {
31512            for (i, (_, expr)) in s.fields.iter().enumerate() {
31513                if i > 0 {
31514                    self.write(", ");
31515                }
31516                if let Expression::PropertyEQ(op) = expr {
31517                    self.generate_expression(&op.left)?;
31518                    if is_duckdb {
31519                        self.write(": ");
31520                    } else {
31521                        self.write(" => ");
31522                    }
31523                    self.generate_expression(&op.right)?;
31524                } else {
31525                    self.generate_expression(expr)?;
31526                }
31527            }
31528        }
31529        if is_duckdb {
31530            self.write("}");
31531        } else {
31532            self.write("]");
31533        }
31534        Ok(())
31535    }
31536
31537    fn generate_localtime(&mut self, e: &Localtime) -> Result<()> {
31538        // Python: LOCALTIME or LOCALTIME(precision)
31539        self.write_keyword("LOCALTIME");
31540        if let Some(precision) = &e.this {
31541            self.write("(");
31542            self.generate_expression(precision)?;
31543            self.write(")");
31544        }
31545        Ok(())
31546    }
31547
31548    fn generate_localtimestamp(&mut self, e: &Localtimestamp) -> Result<()> {
31549        // Python: LOCALTIMESTAMP or LOCALTIMESTAMP(precision)
31550        self.write_keyword("LOCALTIMESTAMP");
31551        if let Some(precision) = &e.this {
31552            self.write("(");
31553            self.generate_expression(precision)?;
31554            self.write(")");
31555        }
31556        Ok(())
31557    }
31558
31559    fn generate_location_property(&mut self, e: &LocationProperty) -> Result<()> {
31560        // LOCATION 'path'
31561        self.write_keyword("LOCATION");
31562        self.write_space();
31563        self.generate_expression(&e.this)?;
31564        Ok(())
31565    }
31566
31567    fn generate_lock(&mut self, e: &Lock) -> Result<()> {
31568        // Python: FOR UPDATE|FOR SHARE [OF tables] [NOWAIT|WAIT n]
31569        if e.update.is_some() {
31570            if e.key.is_some() {
31571                self.write_keyword("FOR NO KEY UPDATE");
31572            } else {
31573                self.write_keyword("FOR UPDATE");
31574            }
31575        } else {
31576            if e.key.is_some() {
31577                self.write_keyword("FOR KEY SHARE");
31578            } else {
31579                self.write_keyword("FOR SHARE");
31580            }
31581        }
31582        if !e.expressions.is_empty() {
31583            self.write_keyword(" OF ");
31584            for (i, expr) in e.expressions.iter().enumerate() {
31585                if i > 0 {
31586                    self.write(", ");
31587                }
31588                self.generate_expression(expr)?;
31589            }
31590        }
31591        // Handle wait option following Python sqlglot convention:
31592        // - Boolean(true) -> NOWAIT
31593        // - Boolean(false) -> SKIP LOCKED
31594        // - Literal (number) -> WAIT n
31595        if let Some(wait) = &e.wait {
31596            match wait.as_ref() {
31597                Expression::Boolean(b) => {
31598                    if b.value {
31599                        self.write_keyword(" NOWAIT");
31600                    } else {
31601                        self.write_keyword(" SKIP LOCKED");
31602                    }
31603                }
31604                _ => {
31605                    // It's a literal (number), output WAIT n
31606                    self.write_keyword(" WAIT ");
31607                    self.generate_expression(wait)?;
31608                }
31609            }
31610        }
31611        Ok(())
31612    }
31613
31614    fn generate_lock_property(&mut self, e: &LockProperty) -> Result<()> {
31615        // LOCK property
31616        self.write_keyword("LOCK");
31617        self.write_space();
31618        self.generate_expression(&e.this)?;
31619        Ok(())
31620    }
31621
31622    fn generate_locking_property(&mut self, e: &LockingProperty) -> Result<()> {
31623        // Python: LOCKING kind [this] [for_or_in] lock_type [OVERRIDE]
31624        self.write_keyword("LOCKING");
31625        self.write_space();
31626        self.write(&e.kind);
31627        if let Some(this) = &e.this {
31628            self.write_space();
31629            self.generate_expression(this)?;
31630        }
31631        if let Some(for_or_in) = &e.for_or_in {
31632            self.write_space();
31633            self.generate_expression(for_or_in)?;
31634        }
31635        if let Some(lock_type) = &e.lock_type {
31636            self.write_space();
31637            self.generate_expression(lock_type)?;
31638        }
31639        if e.override_.is_some() {
31640            self.write_keyword(" OVERRIDE");
31641        }
31642        Ok(())
31643    }
31644
31645    fn generate_locking_statement(&mut self, e: &LockingStatement) -> Result<()> {
31646        // this expression
31647        self.generate_expression(&e.this)?;
31648        self.write_space();
31649        self.generate_expression(&e.expression)?;
31650        Ok(())
31651    }
31652
31653    fn generate_log_property(&mut self, e: &LogProperty) -> Result<()> {
31654        // [NO] LOG
31655        if e.no.is_some() {
31656            self.write_keyword("NO ");
31657        }
31658        self.write_keyword("LOG");
31659        Ok(())
31660    }
31661
31662    fn generate_md5_digest(&mut self, e: &MD5Digest) -> Result<()> {
31663        // MD5(this, expressions...)
31664        self.write_keyword("MD5");
31665        self.write("(");
31666        self.generate_expression(&e.this)?;
31667        for expr in &e.expressions {
31668            self.write(", ");
31669            self.generate_expression(expr)?;
31670        }
31671        self.write(")");
31672        Ok(())
31673    }
31674
31675    fn generate_ml_forecast(&mut self, e: &MLForecast) -> Result<()> {
31676        // ML.FORECAST(model, [params])
31677        self.write_keyword("ML.FORECAST");
31678        self.write("(");
31679        self.generate_expression(&e.this)?;
31680        if let Some(expression) = &e.expression {
31681            self.write(", ");
31682            self.generate_expression(expression)?;
31683        }
31684        if let Some(params) = &e.params_struct {
31685            self.write(", ");
31686            self.generate_expression(params)?;
31687        }
31688        self.write(")");
31689        Ok(())
31690    }
31691
31692    fn generate_ml_translate(&mut self, e: &MLTranslate) -> Result<()> {
31693        // ML.TRANSLATE(model, input, [params])
31694        self.write_keyword("ML.TRANSLATE");
31695        self.write("(");
31696        self.generate_expression(&e.this)?;
31697        self.write(", ");
31698        self.generate_expression(&e.expression)?;
31699        if let Some(params) = &e.params_struct {
31700            self.write(", ");
31701            self.generate_expression(params)?;
31702        }
31703        self.write(")");
31704        Ok(())
31705    }
31706
31707    fn generate_make_interval(&mut self, e: &MakeInterval) -> Result<()> {
31708        // MAKE_INTERVAL(years => x, months => y, ...)
31709        self.write_keyword("MAKE_INTERVAL");
31710        self.write("(");
31711        let mut first = true;
31712        if let Some(year) = &e.year {
31713            self.write("years => ");
31714            self.generate_expression(year)?;
31715            first = false;
31716        }
31717        if let Some(month) = &e.month {
31718            if !first {
31719                self.write(", ");
31720            }
31721            self.write("months => ");
31722            self.generate_expression(month)?;
31723            first = false;
31724        }
31725        if let Some(week) = &e.week {
31726            if !first {
31727                self.write(", ");
31728            }
31729            self.write("weeks => ");
31730            self.generate_expression(week)?;
31731            first = false;
31732        }
31733        if let Some(day) = &e.day {
31734            if !first {
31735                self.write(", ");
31736            }
31737            self.write("days => ");
31738            self.generate_expression(day)?;
31739            first = false;
31740        }
31741        if let Some(hour) = &e.hour {
31742            if !first {
31743                self.write(", ");
31744            }
31745            self.write("hours => ");
31746            self.generate_expression(hour)?;
31747            first = false;
31748        }
31749        if let Some(minute) = &e.minute {
31750            if !first {
31751                self.write(", ");
31752            }
31753            self.write("mins => ");
31754            self.generate_expression(minute)?;
31755            first = false;
31756        }
31757        if let Some(second) = &e.second {
31758            if !first {
31759                self.write(", ");
31760            }
31761            self.write("secs => ");
31762            self.generate_expression(second)?;
31763        }
31764        self.write(")");
31765        Ok(())
31766    }
31767
31768    fn generate_manhattan_distance(&mut self, e: &ManhattanDistance) -> Result<()> {
31769        // MANHATTAN_DISTANCE(vector1, vector2)
31770        self.write_keyword("MANHATTAN_DISTANCE");
31771        self.write("(");
31772        self.generate_expression(&e.this)?;
31773        self.write(", ");
31774        self.generate_expression(&e.expression)?;
31775        self.write(")");
31776        Ok(())
31777    }
31778
31779    fn generate_map(&mut self, e: &Map) -> Result<()> {
31780        // MAP(key1, value1, key2, value2, ...)
31781        self.write_keyword("MAP");
31782        self.write("(");
31783        for (i, (key, value)) in e.keys.iter().zip(e.values.iter()).enumerate() {
31784            if i > 0 {
31785                self.write(", ");
31786            }
31787            self.generate_expression(key)?;
31788            self.write(", ");
31789            self.generate_expression(value)?;
31790        }
31791        self.write(")");
31792        Ok(())
31793    }
31794
31795    fn generate_map_cat(&mut self, e: &MapCat) -> Result<()> {
31796        // MAP_CAT(map1, map2)
31797        self.write_keyword("MAP_CAT");
31798        self.write("(");
31799        self.generate_expression(&e.this)?;
31800        self.write(", ");
31801        self.generate_expression(&e.expression)?;
31802        self.write(")");
31803        Ok(())
31804    }
31805
31806    fn generate_map_delete(&mut self, e: &MapDelete) -> Result<()> {
31807        // MAP_DELETE(map, key1, key2, ...)
31808        self.write_keyword("MAP_DELETE");
31809        self.write("(");
31810        self.generate_expression(&e.this)?;
31811        for expr in &e.expressions {
31812            self.write(", ");
31813            self.generate_expression(expr)?;
31814        }
31815        self.write(")");
31816        Ok(())
31817    }
31818
31819    fn generate_map_insert(&mut self, e: &MapInsert) -> Result<()> {
31820        // MAP_INSERT(map, key, value, [update_flag])
31821        self.write_keyword("MAP_INSERT");
31822        self.write("(");
31823        self.generate_expression(&e.this)?;
31824        if let Some(key) = &e.key {
31825            self.write(", ");
31826            self.generate_expression(key)?;
31827        }
31828        if let Some(value) = &e.value {
31829            self.write(", ");
31830            self.generate_expression(value)?;
31831        }
31832        if let Some(update_flag) = &e.update_flag {
31833            self.write(", ");
31834            self.generate_expression(update_flag)?;
31835        }
31836        self.write(")");
31837        Ok(())
31838    }
31839
31840    fn generate_map_pick(&mut self, e: &MapPick) -> Result<()> {
31841        // MAP_PICK(map, key1, key2, ...)
31842        self.write_keyword("MAP_PICK");
31843        self.write("(");
31844        self.generate_expression(&e.this)?;
31845        for expr in &e.expressions {
31846            self.write(", ");
31847            self.generate_expression(expr)?;
31848        }
31849        self.write(")");
31850        Ok(())
31851    }
31852
31853    fn generate_masking_policy_column_constraint(
31854        &mut self,
31855        e: &MaskingPolicyColumnConstraint,
31856    ) -> Result<()> {
31857        // Python: MASKING POLICY name [USING (cols)]
31858        self.write_keyword("MASKING POLICY");
31859        self.write_space();
31860        self.generate_expression(&e.this)?;
31861        if !e.expressions.is_empty() {
31862            self.write_keyword(" USING");
31863            self.write(" (");
31864            for (i, expr) in e.expressions.iter().enumerate() {
31865                if i > 0 {
31866                    self.write(", ");
31867                }
31868                self.generate_expression(expr)?;
31869            }
31870            self.write(")");
31871        }
31872        Ok(())
31873    }
31874
31875    fn generate_match_against(&mut self, e: &MatchAgainst) -> Result<()> {
31876        if matches!(
31877            self.config.dialect,
31878            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
31879        ) {
31880            if e.expressions.len() > 1 {
31881                self.write("(");
31882            }
31883            for (i, expr) in e.expressions.iter().enumerate() {
31884                if i > 0 {
31885                    self.write_keyword(" OR ");
31886                }
31887                self.generate_expression(expr)?;
31888                self.write_space();
31889                self.write("@@");
31890                self.write_space();
31891                self.generate_expression(&e.this)?;
31892            }
31893            if e.expressions.len() > 1 {
31894                self.write(")");
31895            }
31896            return Ok(());
31897        }
31898
31899        // MATCH(columns) AGAINST(expr [modifier])
31900        self.write_keyword("MATCH");
31901        self.write("(");
31902        for (i, expr) in e.expressions.iter().enumerate() {
31903            if i > 0 {
31904                self.write(", ");
31905            }
31906            self.generate_expression(expr)?;
31907        }
31908        self.write(")");
31909        self.write_keyword(" AGAINST");
31910        self.write("(");
31911        self.generate_expression(&e.this)?;
31912        if let Some(modifier) = &e.modifier {
31913            self.write_space();
31914            self.generate_expression(modifier)?;
31915        }
31916        self.write(")");
31917        Ok(())
31918    }
31919
31920    fn generate_match_recognize_measure(&mut self, e: &MatchRecognizeMeasure) -> Result<()> {
31921        // Python: [window_frame] this
31922        if let Some(window_frame) = &e.window_frame {
31923            self.write(&format!("{:?}", window_frame).to_ascii_uppercase());
31924            self.write_space();
31925        }
31926        self.generate_expression(&e.this)?;
31927        Ok(())
31928    }
31929
31930    fn generate_materialized_property(&mut self, e: &MaterializedProperty) -> Result<()> {
31931        // MATERIALIZED [this]
31932        self.write_keyword("MATERIALIZED");
31933        if let Some(this) = &e.this {
31934            self.write_space();
31935            self.generate_expression(this)?;
31936        }
31937        Ok(())
31938    }
31939
31940    fn generate_merge(&mut self, e: &Merge) -> Result<()> {
31941        // MERGE INTO target USING source ON condition WHEN ...
31942        // DuckDB variant: MERGE INTO target USING source USING (key_columns) WHEN ...
31943        if let Some(with_) = &e.with_ {
31944            if let Expression::With(with_clause) = with_.as_ref() {
31945                self.generate_with(with_clause)?;
31946                self.write_space();
31947            } else {
31948                self.generate_expression(with_)?;
31949                self.write_space();
31950            }
31951        }
31952        self.write_keyword("MERGE INTO");
31953        self.write_space();
31954        if matches!(self.config.dialect, Some(crate::DialectType::Oracle)) {
31955            if let Expression::Alias(alias) = e.this.as_ref() {
31956                self.generate_expression(&alias.this)?;
31957                self.write_space();
31958                self.generate_identifier(&alias.alias)?;
31959            } else {
31960                self.generate_expression(&e.this)?;
31961            }
31962        } else {
31963            self.generate_expression(&e.this)?;
31964        }
31965
31966        // USING clause - newline before in pretty mode
31967        if self.config.pretty {
31968            self.write_newline();
31969            self.write_indent();
31970        } else {
31971            self.write_space();
31972        }
31973        self.write_keyword("USING");
31974        self.write_space();
31975        self.generate_expression(&e.using)?;
31976
31977        // ON clause - newline before in pretty mode
31978        if let Some(on) = &e.on {
31979            if self.config.pretty {
31980                self.write_newline();
31981                self.write_indent();
31982            } else {
31983                self.write_space();
31984            }
31985            self.write_keyword("ON");
31986            self.write_space();
31987            self.generate_expression(on)?;
31988        }
31989        // DuckDB USING (key_columns) clause
31990        if let Some(using_cond) = &e.using_cond {
31991            self.write_space();
31992            self.write_keyword("USING");
31993            self.write_space();
31994            self.write("(");
31995            // using_cond is a Tuple containing the column identifiers
31996            if let Expression::Tuple(tuple) = using_cond.as_ref() {
31997                for (i, col) in tuple.expressions.iter().enumerate() {
31998                    if i > 0 {
31999                        self.write(", ");
32000                    }
32001                    self.generate_expression(col)?;
32002                }
32003            } else {
32004                self.generate_expression(using_cond)?;
32005            }
32006            self.write(")");
32007        }
32008        // For PostgreSQL dialect, extract target table name/alias to strip from UPDATE SET
32009        let saved_merge_strip = std::mem::take(&mut self.merge_strip_qualifiers);
32010        if matches!(
32011            self.config.dialect,
32012            Some(crate::DialectType::PostgreSQL)
32013                | Some(crate::DialectType::Redshift)
32014                | Some(crate::DialectType::Trino)
32015                | Some(crate::DialectType::Presto)
32016                | Some(crate::DialectType::Athena)
32017        ) {
32018            let mut names = Vec::new();
32019            match e.this.as_ref() {
32020                Expression::Alias(a) => {
32021                    // e.g., "x AS z" -> strip both "x" and "z"
32022                    if let Expression::Table(t) = &a.this {
32023                        names.push(t.name.name.clone());
32024                    } else if let Expression::Identifier(id) = &a.this {
32025                        names.push(id.name.clone());
32026                    }
32027                    names.push(a.alias.name.clone());
32028                }
32029                Expression::Table(t) => {
32030                    names.push(t.name.name.clone());
32031                }
32032                Expression::Identifier(id) => {
32033                    names.push(id.name.clone());
32034                }
32035                _ => {}
32036            }
32037            self.merge_strip_qualifiers = names;
32038        }
32039
32040        // WHEN clauses - newline before each in pretty mode
32041        if let Some(whens) = &e.whens {
32042            if self.config.pretty {
32043                self.write_newline();
32044                self.write_indent();
32045            } else {
32046                self.write_space();
32047            }
32048            self.generate_expression(whens)?;
32049        }
32050
32051        // Restore merge_strip_qualifiers
32052        self.merge_strip_qualifiers = saved_merge_strip;
32053
32054        // OUTPUT/RETURNING clause - newline before in pretty mode
32055        if let Some(returning) = &e.returning {
32056            if self.config.pretty {
32057                self.write_newline();
32058                self.write_indent();
32059            } else {
32060                self.write_space();
32061            }
32062            self.generate_expression(returning)?;
32063        }
32064        Ok(())
32065    }
32066
32067    fn generate_merge_block_ratio_property(&mut self, e: &MergeBlockRatioProperty) -> Result<()> {
32068        // Python: NO MERGEBLOCKRATIO | DEFAULT MERGEBLOCKRATIO | MERGEBLOCKRATIO=this [PERCENT]
32069        if e.no.is_some() {
32070            self.write_keyword("NO MERGEBLOCKRATIO");
32071        } else if e.default.is_some() {
32072            self.write_keyword("DEFAULT MERGEBLOCKRATIO");
32073        } else {
32074            self.write_keyword("MERGEBLOCKRATIO");
32075            self.write("=");
32076            if let Some(this) = &e.this {
32077                self.generate_expression(this)?;
32078            }
32079            if e.percent.is_some() {
32080                self.write_keyword(" PERCENT");
32081            }
32082        }
32083        Ok(())
32084    }
32085
32086    fn generate_merge_tree_ttl(&mut self, e: &MergeTreeTTL) -> Result<()> {
32087        // TTL expressions [WHERE where] [GROUP BY group] [SET aggregates]
32088        self.write_keyword("TTL");
32089        let pretty_clickhouse = self.config.pretty
32090            && matches!(
32091                self.config.dialect,
32092                Some(crate::dialects::DialectType::ClickHouse)
32093            );
32094
32095        if pretty_clickhouse {
32096            self.write_newline();
32097            self.indent_level += 1;
32098            for (i, expr) in e.expressions.iter().enumerate() {
32099                if i > 0 {
32100                    self.write(",");
32101                    self.write_newline();
32102                }
32103                self.write_indent();
32104                self.generate_expression(expr)?;
32105            }
32106            self.indent_level -= 1;
32107        } else {
32108            self.write_space();
32109            for (i, expr) in e.expressions.iter().enumerate() {
32110                if i > 0 {
32111                    self.write(", ");
32112                }
32113                self.generate_expression(expr)?;
32114            }
32115        }
32116
32117        if let Some(where_) = &e.where_ {
32118            if pretty_clickhouse {
32119                self.write_newline();
32120                if let Expression::Where(w) = where_.as_ref() {
32121                    self.write_indent();
32122                    self.write_keyword("WHERE");
32123                    self.write_newline();
32124                    self.indent_level += 1;
32125                    self.write_indent();
32126                    self.generate_expression(&w.this)?;
32127                    self.indent_level -= 1;
32128                } else {
32129                    self.write_indent();
32130                    self.generate_expression(where_)?;
32131                }
32132            } else {
32133                self.write_space();
32134                self.generate_expression(where_)?;
32135            }
32136        }
32137        if let Some(group) = &e.group {
32138            if pretty_clickhouse {
32139                self.write_newline();
32140                if let Expression::Group(g) = group.as_ref() {
32141                    self.write_indent();
32142                    self.write_keyword("GROUP BY");
32143                    self.write_newline();
32144                    self.indent_level += 1;
32145                    for (i, expr) in g.expressions.iter().enumerate() {
32146                        if i > 0 {
32147                            self.write(",");
32148                            self.write_newline();
32149                        }
32150                        self.write_indent();
32151                        self.generate_expression(expr)?;
32152                    }
32153                    self.indent_level -= 1;
32154                } else {
32155                    self.write_indent();
32156                    self.generate_expression(group)?;
32157                }
32158            } else {
32159                self.write_space();
32160                self.generate_expression(group)?;
32161            }
32162        }
32163        if let Some(aggregates) = &e.aggregates {
32164            if pretty_clickhouse {
32165                self.write_newline();
32166                self.write_indent();
32167                self.write_keyword("SET");
32168                self.write_newline();
32169                self.indent_level += 1;
32170                if let Expression::Tuple(t) = aggregates.as_ref() {
32171                    for (i, agg) in t.expressions.iter().enumerate() {
32172                        if i > 0 {
32173                            self.write(",");
32174                            self.write_newline();
32175                        }
32176                        self.write_indent();
32177                        self.generate_expression(agg)?;
32178                    }
32179                } else {
32180                    self.write_indent();
32181                    self.generate_expression(aggregates)?;
32182                }
32183                self.indent_level -= 1;
32184            } else {
32185                self.write_space();
32186                self.write_keyword("SET");
32187                self.write_space();
32188                if let Expression::Tuple(t) = aggregates.as_ref() {
32189                    for (i, agg) in t.expressions.iter().enumerate() {
32190                        if i > 0 {
32191                            self.write(", ");
32192                        }
32193                        self.generate_expression(agg)?;
32194                    }
32195                } else {
32196                    self.generate_expression(aggregates)?;
32197                }
32198            }
32199        }
32200        Ok(())
32201    }
32202
32203    fn generate_merge_tree_ttl_action(&mut self, e: &MergeTreeTTLAction) -> Result<()> {
32204        // Python: this [DELETE] [RECOMPRESS codec] [TO DISK disk] [TO VOLUME volume]
32205        self.generate_expression(&e.this)?;
32206        if e.delete.is_some() {
32207            self.write_keyword(" DELETE");
32208        }
32209        if let Some(recompress) = &e.recompress {
32210            self.write_keyword(" RECOMPRESS ");
32211            self.generate_expression(recompress)?;
32212        }
32213        if let Some(to_disk) = &e.to_disk {
32214            self.write_keyword(" TO DISK ");
32215            self.generate_expression(to_disk)?;
32216        }
32217        if let Some(to_volume) = &e.to_volume {
32218            self.write_keyword(" TO VOLUME ");
32219            self.generate_expression(to_volume)?;
32220        }
32221        Ok(())
32222    }
32223
32224    fn generate_minhash(&mut self, e: &Minhash) -> Result<()> {
32225        // MINHASH(this, expressions...)
32226        self.write_keyword("MINHASH");
32227        self.write("(");
32228        self.generate_expression(&e.this)?;
32229        for expr in &e.expressions {
32230            self.write(", ");
32231            self.generate_expression(expr)?;
32232        }
32233        self.write(")");
32234        Ok(())
32235    }
32236
32237    fn generate_model_attribute(&mut self, e: &ModelAttribute) -> Result<()> {
32238        // model!attribute - Snowflake syntax
32239        self.generate_expression(&e.this)?;
32240        self.write("!");
32241        self.generate_expression(&e.expression)?;
32242        Ok(())
32243    }
32244
32245    fn generate_monthname(&mut self, e: &Monthname) -> Result<()> {
32246        // MONTHNAME(this)
32247        self.write_keyword("MONTHNAME");
32248        self.write("(");
32249        self.generate_expression(&e.this)?;
32250        self.write(")");
32251        Ok(())
32252    }
32253
32254    fn generate_multitable_inserts(&mut self, e: &MultitableInserts) -> Result<()> {
32255        // Output leading comments
32256        for comment in &e.leading_comments {
32257            self.write_formatted_comment(comment);
32258            if self.config.pretty {
32259                self.write_newline();
32260                self.write_indent();
32261            } else {
32262                self.write_space();
32263            }
32264        }
32265        // Python: INSERT [OVERWRITE] kind expressions source
32266        self.write_keyword("INSERT");
32267        if e.overwrite {
32268            self.write_space();
32269            self.write_keyword("OVERWRITE");
32270        }
32271        self.write_space();
32272        self.write(&e.kind);
32273        if self.config.pretty {
32274            self.indent_level += 1;
32275            for expr in &e.expressions {
32276                self.write_newline();
32277                self.write_indent();
32278                self.generate_expression(expr)?;
32279            }
32280            self.indent_level -= 1;
32281        } else {
32282            for expr in &e.expressions {
32283                self.write_space();
32284                self.generate_expression(expr)?;
32285            }
32286        }
32287        if let Some(source) = &e.source {
32288            if self.config.pretty {
32289                self.write_newline();
32290                self.write_indent();
32291            } else {
32292                self.write_space();
32293            }
32294            self.generate_expression(source)?;
32295        }
32296        Ok(())
32297    }
32298
32299    fn generate_next_value_for(&mut self, e: &NextValueFor) -> Result<()> {
32300        // Python: NEXT VALUE FOR this [OVER (order)]
32301        self.write_keyword("NEXT VALUE FOR");
32302        self.write_space();
32303        self.generate_expression(&e.this)?;
32304        if let Some(order) = &e.order {
32305            self.write_space();
32306            self.write_keyword("OVER");
32307            self.write(" (");
32308            self.generate_expression(order)?;
32309            self.write(")");
32310        }
32311        Ok(())
32312    }
32313
32314    fn generate_normal(&mut self, e: &Normal) -> Result<()> {
32315        // NORMAL(mean, stddev, gen)
32316        self.write_keyword("NORMAL");
32317        self.write("(");
32318        self.generate_expression(&e.this)?;
32319        if let Some(stddev) = &e.stddev {
32320            self.write(", ");
32321            self.generate_expression(stddev)?;
32322        }
32323        if let Some(gen) = &e.gen {
32324            self.write(", ");
32325            self.generate_expression(gen)?;
32326        }
32327        self.write(")");
32328        Ok(())
32329    }
32330
32331    fn generate_normalize(&mut self, e: &Normalize) -> Result<()> {
32332        // NORMALIZE(this, form) or CASEFOLD version
32333        if e.is_casefold.is_some() {
32334            self.write_keyword("NORMALIZE_AND_CASEFOLD");
32335        } else {
32336            self.write_keyword("NORMALIZE");
32337        }
32338        self.write("(");
32339        self.generate_expression(&e.this)?;
32340        if let Some(form) = &e.form {
32341            self.write(", ");
32342            self.generate_expression(form)?;
32343        }
32344        self.write(")");
32345        Ok(())
32346    }
32347
32348    fn generate_not_null_column_constraint(&mut self, e: &NotNullColumnConstraint) -> Result<()> {
32349        // Python: [NOT ]NULL
32350        if e.allow_null.is_none() {
32351            self.write_keyword("NOT ");
32352        }
32353        self.write_keyword("NULL");
32354        Ok(())
32355    }
32356
32357    fn generate_nullif(&mut self, e: &Nullif) -> Result<()> {
32358        // NULLIF(this, expression)
32359        self.write_keyword("NULLIF");
32360        self.write("(");
32361        self.generate_expression(&e.this)?;
32362        self.write(", ");
32363        self.generate_expression(&e.expression)?;
32364        self.write(")");
32365        Ok(())
32366    }
32367
32368    fn generate_number_to_str(&mut self, e: &NumberToStr) -> Result<()> {
32369        // FORMAT(this, format, culture)
32370        self.write_keyword("FORMAT");
32371        self.write("(");
32372        self.generate_expression(&e.this)?;
32373        self.write(", '");
32374        self.write(&e.format);
32375        self.write("'");
32376        if let Some(culture) = &e.culture {
32377            self.write(", ");
32378            self.generate_expression(culture)?;
32379        }
32380        self.write(")");
32381        Ok(())
32382    }
32383
32384    fn generate_object_agg(&mut self, e: &ObjectAgg) -> Result<()> {
32385        // OBJECT_AGG(key, value)
32386        self.write_keyword("OBJECT_AGG");
32387        self.write("(");
32388        self.generate_expression(&e.this)?;
32389        self.write(", ");
32390        self.generate_expression(&e.expression)?;
32391        self.write(")");
32392        Ok(())
32393    }
32394
32395    fn generate_object_identifier(&mut self, e: &ObjectIdentifier) -> Result<()> {
32396        // Python: Just returns the name
32397        self.generate_expression(&e.this)?;
32398        Ok(())
32399    }
32400
32401    fn generate_object_insert(&mut self, e: &ObjectInsert) -> Result<()> {
32402        // OBJECT_INSERT(obj, key, value, [update_flag])
32403        self.write_keyword("OBJECT_INSERT");
32404        self.write("(");
32405        self.generate_expression(&e.this)?;
32406        if let Some(key) = &e.key {
32407            self.write(", ");
32408            self.generate_expression(key)?;
32409        }
32410        if let Some(value) = &e.value {
32411            self.write(", ");
32412            self.generate_expression(value)?;
32413        }
32414        if let Some(update_flag) = &e.update_flag {
32415            self.write(", ");
32416            self.generate_expression(update_flag)?;
32417        }
32418        self.write(")");
32419        Ok(())
32420    }
32421
32422    fn generate_offset(&mut self, e: &Offset) -> Result<()> {
32423        // OFFSET value [ROW|ROWS]
32424        self.write_keyword("OFFSET");
32425        self.write_space();
32426        self.generate_expression(&e.this)?;
32427        // Output ROWS keyword only for TSQL/Oracle targets
32428        if e.rows == Some(true)
32429            && matches!(
32430                self.config.dialect,
32431                Some(crate::dialects::DialectType::TSQL)
32432                    | Some(crate::dialects::DialectType::Oracle)
32433            )
32434        {
32435            self.write_space();
32436            self.write_keyword("ROWS");
32437        }
32438        Ok(())
32439    }
32440
32441    fn generate_qualify(&mut self, e: &Qualify) -> Result<()> {
32442        // QUALIFY condition (Snowflake/BigQuery)
32443        self.write_keyword("QUALIFY");
32444        self.write_space();
32445        self.generate_expression(&e.this)?;
32446        Ok(())
32447    }
32448
32449    fn generate_on_cluster(&mut self, e: &OnCluster) -> Result<()> {
32450        // ON CLUSTER cluster_name
32451        self.write_keyword("ON CLUSTER");
32452        self.write_space();
32453        self.generate_expression(&e.this)?;
32454        Ok(())
32455    }
32456
32457    fn generate_on_commit_property(&mut self, e: &OnCommitProperty) -> Result<()> {
32458        // ON COMMIT [DELETE ROWS | PRESERVE ROWS]
32459        self.write_keyword("ON COMMIT");
32460        if e.delete.is_some() {
32461            self.write_keyword(" DELETE ROWS");
32462        } else {
32463            self.write_keyword(" PRESERVE ROWS");
32464        }
32465        Ok(())
32466    }
32467
32468    fn generate_on_condition(&mut self, e: &OnCondition) -> Result<()> {
32469        // Python: error/empty/null handling
32470        if let Some(empty) = &e.empty {
32471            self.generate_expression(empty)?;
32472            self.write_keyword(" ON EMPTY");
32473        }
32474        if let Some(error) = &e.error {
32475            if e.empty.is_some() {
32476                self.write_space();
32477            }
32478            self.generate_expression(error)?;
32479            self.write_keyword(" ON ERROR");
32480        }
32481        if let Some(null) = &e.null {
32482            if e.empty.is_some() || e.error.is_some() {
32483                self.write_space();
32484            }
32485            self.generate_expression(null)?;
32486            self.write_keyword(" ON NULL");
32487        }
32488        Ok(())
32489    }
32490
32491    fn generate_on_conflict(&mut self, e: &OnConflict) -> Result<()> {
32492        // Materialize doesn't support ON CONFLICT - skip entirely
32493        if matches!(self.config.dialect, Some(DialectType::Materialize)) {
32494            return Ok(());
32495        }
32496        // Python: ON CONFLICT|ON DUPLICATE KEY [ON CONSTRAINT constraint] [conflict_keys] action
32497        if e.duplicate.is_some() {
32498            // MySQL: ON DUPLICATE KEY UPDATE col = val, ...
32499            self.write_keyword("ON DUPLICATE KEY UPDATE");
32500            for (i, expr) in e.expressions.iter().enumerate() {
32501                if i > 0 {
32502                    self.write(",");
32503                }
32504                self.write_space();
32505                self.generate_expression(expr)?;
32506            }
32507            return Ok(());
32508        } else {
32509            self.write_keyword("ON CONFLICT");
32510        }
32511        if let Some(constraint) = &e.constraint {
32512            self.write_keyword(" ON CONSTRAINT ");
32513            self.generate_expression(constraint)?;
32514        }
32515        if let Some(conflict_keys) = &e.conflict_keys {
32516            // conflict_keys can be a Tuple containing expressions
32517            if let Expression::Tuple(t) = conflict_keys.as_ref() {
32518                self.write("(");
32519                for (i, expr) in t.expressions.iter().enumerate() {
32520                    if i > 0 {
32521                        self.write(", ");
32522                    }
32523                    self.generate_expression(expr)?;
32524                }
32525                self.write(")");
32526            } else {
32527                self.write("(");
32528                self.generate_expression(conflict_keys)?;
32529                self.write(")");
32530            }
32531        }
32532        if let Some(index_predicate) = &e.index_predicate {
32533            self.write_keyword(" WHERE ");
32534            self.generate_expression(index_predicate)?;
32535        }
32536        if let Some(action) = &e.action {
32537            // Check if action is "NOTHING" or an UPDATE set
32538            if let Expression::Identifier(id) = action.as_ref() {
32539                if id.name.eq_ignore_ascii_case("NOTHING") {
32540                    self.write_keyword(" DO NOTHING");
32541                } else {
32542                    self.write_keyword(" DO ");
32543                    self.generate_expression(action)?;
32544                }
32545            } else if let Expression::Tuple(t) = action.as_ref() {
32546                // DO UPDATE SET col1 = val1, col2 = val2
32547                self.write_keyword(" DO UPDATE SET ");
32548                for (i, expr) in t.expressions.iter().enumerate() {
32549                    if i > 0 {
32550                        self.write(", ");
32551                    }
32552                    self.generate_expression(expr)?;
32553                }
32554            } else {
32555                self.write_keyword(" DO ");
32556                self.generate_expression(action)?;
32557            }
32558        }
32559        // WHERE clause for the UPDATE action
32560        if let Some(where_) = &e.where_ {
32561            self.write_keyword(" WHERE ");
32562            self.generate_expression(where_)?;
32563        }
32564        Ok(())
32565    }
32566
32567    fn generate_on_property(&mut self, e: &OnProperty) -> Result<()> {
32568        // ON property_value
32569        self.write_keyword("ON");
32570        self.write_space();
32571        self.generate_expression(&e.this)?;
32572        Ok(())
32573    }
32574
32575    fn generate_opclass(&mut self, e: &Opclass) -> Result<()> {
32576        // Python: this expression (e.g., column opclass)
32577        self.generate_expression(&e.this)?;
32578        self.write_space();
32579        self.generate_expression(&e.expression)?;
32580        Ok(())
32581    }
32582
32583    fn generate_open_json(&mut self, e: &OpenJSON) -> Result<()> {
32584        // Python: OPENJSON(this[, path]) [WITH (columns)]
32585        self.write_keyword("OPENJSON");
32586        self.write("(");
32587        self.generate_expression(&e.this)?;
32588        if let Some(path) = &e.path {
32589            self.write(", ");
32590            self.generate_expression(path)?;
32591        }
32592        self.write(")");
32593        if !e.expressions.is_empty() {
32594            self.write_keyword(" WITH");
32595            if self.config.pretty {
32596                self.write(" (\n");
32597                self.indent_level += 2;
32598                for (i, expr) in e.expressions.iter().enumerate() {
32599                    if i > 0 {
32600                        self.write(",\n");
32601                    }
32602                    self.write_indent();
32603                    self.generate_expression(expr)?;
32604                }
32605                self.write("\n");
32606                self.indent_level -= 2;
32607                self.write(")");
32608            } else {
32609                self.write(" (");
32610                for (i, expr) in e.expressions.iter().enumerate() {
32611                    if i > 0 {
32612                        self.write(", ");
32613                    }
32614                    self.generate_expression(expr)?;
32615                }
32616                self.write(")");
32617            }
32618        }
32619        Ok(())
32620    }
32621
32622    fn generate_open_json_column_def(&mut self, e: &OpenJSONColumnDef) -> Result<()> {
32623        // Python: this kind [path] [AS JSON]
32624        self.generate_expression(&e.this)?;
32625        self.write_space();
32626        // Use parsed data_type if available, otherwise fall back to kind string
32627        if let Some(ref dt) = e.data_type {
32628            self.generate_data_type(dt)?;
32629        } else if !e.kind.is_empty() {
32630            self.write(&e.kind);
32631        }
32632        if let Some(path) = &e.path {
32633            self.write_space();
32634            self.generate_expression(path)?;
32635        }
32636        if e.as_json.is_some() {
32637            self.write_keyword(" AS JSON");
32638        }
32639        Ok(())
32640    }
32641
32642    fn generate_operator(&mut self, e: &Operator) -> Result<()> {
32643        // this OPERATOR(op) expression
32644        self.generate_expression(&e.this)?;
32645        self.write_space();
32646        if let Some(op) = &e.operator {
32647            self.write_keyword("OPERATOR");
32648            self.write("(");
32649            self.generate_expression(op)?;
32650            self.write(")");
32651        }
32652        // Emit inline comments between OPERATOR() and the RHS
32653        for comment in &e.comments {
32654            self.write_space();
32655            self.write_formatted_comment(comment);
32656        }
32657        self.write_space();
32658        self.generate_expression(&e.expression)?;
32659        Ok(())
32660    }
32661
32662    fn generate_order_by(&mut self, e: &OrderBy) -> Result<()> {
32663        // ORDER BY expr1 [ASC|DESC] [NULLS FIRST|LAST], expr2 ...
32664        self.write_keyword("ORDER BY");
32665        let pretty_clickhouse_single_paren = self.config.pretty
32666            && matches!(self.config.dialect, Some(DialectType::ClickHouse))
32667            && e.expressions.len() == 1
32668            && matches!(e.expressions[0].this, Expression::Paren(ref p) if !matches!(p.this, Expression::Tuple(_)));
32669        let clickhouse_single_tuple = matches!(self.config.dialect, Some(DialectType::ClickHouse))
32670            && e.expressions.len() == 1
32671            && matches!(e.expressions[0].this, Expression::Tuple(_))
32672            && !e.expressions[0].desc
32673            && e.expressions[0].nulls_first.is_none();
32674
32675        if pretty_clickhouse_single_paren {
32676            self.write_space();
32677            if let Expression::Paren(p) = &e.expressions[0].this {
32678                self.write("(");
32679                self.write_newline();
32680                self.indent_level += 1;
32681                self.write_indent();
32682                self.generate_expression(&p.this)?;
32683                self.indent_level -= 1;
32684                self.write_newline();
32685                self.write(")");
32686            }
32687            return Ok(());
32688        }
32689
32690        if clickhouse_single_tuple {
32691            self.write_space();
32692            if let Expression::Tuple(t) = &e.expressions[0].this {
32693                self.write("(");
32694                for (i, expr) in t.expressions.iter().enumerate() {
32695                    if i > 0 {
32696                        self.write(", ");
32697                    }
32698                    self.generate_expression(expr)?;
32699                }
32700                self.write(")");
32701            }
32702            return Ok(());
32703        }
32704
32705        self.write_space();
32706        for (i, ordered) in e.expressions.iter().enumerate() {
32707            if i > 0 {
32708                self.write(", ");
32709            }
32710            self.generate_expression(&ordered.this)?;
32711            if ordered.desc {
32712                self.write_space();
32713                self.write_keyword("DESC");
32714            } else if ordered.explicit_asc {
32715                self.write_space();
32716                self.write_keyword("ASC");
32717            }
32718            if let Some(nulls_first) = ordered.nulls_first {
32719                // In Dremio, NULLS LAST is the default, so skip generating it
32720                let skip_nulls_last =
32721                    !nulls_first && matches!(self.config.dialect, Some(DialectType::Dremio));
32722                if !skip_nulls_last {
32723                    self.write_space();
32724                    self.write_keyword("NULLS");
32725                    self.write_space();
32726                    if nulls_first {
32727                        self.write_keyword("FIRST");
32728                    } else {
32729                        self.write_keyword("LAST");
32730                    }
32731                }
32732            }
32733        }
32734        Ok(())
32735    }
32736
32737    fn generate_output_model_property(&mut self, e: &OutputModelProperty) -> Result<()> {
32738        // OUTPUT(model)
32739        self.write_keyword("OUTPUT");
32740        self.write("(");
32741        if self.config.pretty {
32742            self.indent_level += 1;
32743            self.write_newline();
32744            self.write_indent();
32745            self.generate_expression(&e.this)?;
32746            self.indent_level -= 1;
32747            self.write_newline();
32748        } else {
32749            self.generate_expression(&e.this)?;
32750        }
32751        self.write(")");
32752        Ok(())
32753    }
32754
32755    fn generate_overflow_truncate_behavior(&mut self, e: &OverflowTruncateBehavior) -> Result<()> {
32756        // Python: TRUNCATE [filler] WITH|WITHOUT COUNT
32757        self.write_keyword("TRUNCATE");
32758        if let Some(this) = &e.this {
32759            self.write_space();
32760            self.generate_expression(this)?;
32761        }
32762        if e.with_count.is_some() {
32763            self.write_keyword(" WITH COUNT");
32764        } else {
32765            self.write_keyword(" WITHOUT COUNT");
32766        }
32767        Ok(())
32768    }
32769
32770    fn generate_parameterized_agg(&mut self, e: &ParameterizedAgg) -> Result<()> {
32771        // Python: name(expressions)(params)
32772        self.generate_expression(&e.this)?;
32773        self.write("(");
32774        for (i, expr) in e.expressions.iter().enumerate() {
32775            if i > 0 {
32776                self.write(", ");
32777            }
32778            self.generate_expression(expr)?;
32779        }
32780        self.write(")(");
32781        for (i, param) in e.params.iter().enumerate() {
32782            if i > 0 {
32783                self.write(", ");
32784            }
32785            self.generate_expression(param)?;
32786        }
32787        self.write(")");
32788        Ok(())
32789    }
32790
32791    fn generate_parse_datetime(&mut self, e: &ParseDatetime) -> Result<()> {
32792        // PARSE_DATETIME(format, this) or similar
32793        self.write_keyword("PARSE_DATETIME");
32794        self.write("(");
32795        if let Some(format) = &e.format {
32796            self.write("'");
32797            self.write(format);
32798            self.write("', ");
32799        }
32800        self.generate_expression(&e.this)?;
32801        if let Some(zone) = &e.zone {
32802            self.write(", ");
32803            self.generate_expression(zone)?;
32804        }
32805        self.write(")");
32806        Ok(())
32807    }
32808
32809    fn generate_parse_ip(&mut self, e: &ParseIp) -> Result<()> {
32810        // PARSE_IP(this, type, permissive)
32811        self.write_keyword("PARSE_IP");
32812        self.write("(");
32813        self.generate_expression(&e.this)?;
32814        if let Some(type_) = &e.type_ {
32815            self.write(", ");
32816            self.generate_expression(type_)?;
32817        }
32818        if let Some(permissive) = &e.permissive {
32819            self.write(", ");
32820            self.generate_expression(permissive)?;
32821        }
32822        self.write(")");
32823        Ok(())
32824    }
32825
32826    fn generate_parse_json(&mut self, e: &ParseJSON) -> Result<()> {
32827        // PARSE_JSON(this, [expression])
32828        self.write_keyword("PARSE_JSON");
32829        self.write("(");
32830        self.generate_expression(&e.this)?;
32831        if let Some(expression) = &e.expression {
32832            self.write(", ");
32833            self.generate_expression(expression)?;
32834        }
32835        self.write(")");
32836        Ok(())
32837    }
32838
32839    fn generate_parse_time(&mut self, e: &ParseTime) -> Result<()> {
32840        // PARSE_TIME(format, this) or STR_TO_TIME(this, format)
32841        self.write_keyword("PARSE_TIME");
32842        self.write("(");
32843        self.write(&format!("'{}'", e.format));
32844        self.write(", ");
32845        self.generate_expression(&e.this)?;
32846        self.write(")");
32847        Ok(())
32848    }
32849
32850    fn generate_parse_url(&mut self, e: &ParseUrl) -> Result<()> {
32851        // PARSE_URL(this, [part_to_extract], [key], [permissive])
32852        self.write_keyword("PARSE_URL");
32853        self.write("(");
32854        self.generate_expression(&e.this)?;
32855        if let Some(part) = &e.part_to_extract {
32856            self.write(", ");
32857            self.generate_expression(part)?;
32858        }
32859        if let Some(key) = &e.key {
32860            self.write(", ");
32861            self.generate_expression(key)?;
32862        }
32863        if let Some(permissive) = &e.permissive {
32864            self.write(", ");
32865            self.generate_expression(permissive)?;
32866        }
32867        self.write(")");
32868        Ok(())
32869    }
32870
32871    fn generate_partition_expr(&mut self, e: &Partition) -> Result<()> {
32872        // PARTITION(expr1, expr2, ...) or SUBPARTITION(expr1, expr2, ...)
32873        if e.subpartition {
32874            self.write_keyword("SUBPARTITION");
32875        } else {
32876            self.write_keyword("PARTITION");
32877        }
32878        self.write("(");
32879        for (i, expr) in e.expressions.iter().enumerate() {
32880            if i > 0 {
32881                self.write(", ");
32882            }
32883            self.generate_expression(expr)?;
32884        }
32885        self.write(")");
32886        Ok(())
32887    }
32888
32889    fn generate_partition_bound_spec(&mut self, e: &PartitionBoundSpec) -> Result<()> {
32890        // IN (values) or WITH (MODULUS this, REMAINDER expression) or FROM (from) TO (to)
32891        if let Some(this) = &e.this {
32892            if let Some(expression) = &e.expression {
32893                // WITH (MODULUS this, REMAINDER expression)
32894                self.write_keyword("WITH");
32895                self.write(" (");
32896                self.write_keyword("MODULUS");
32897                self.write_space();
32898                self.generate_expression(this)?;
32899                self.write(", ");
32900                self.write_keyword("REMAINDER");
32901                self.write_space();
32902                self.generate_expression(expression)?;
32903                self.write(")");
32904            } else {
32905                // IN (this) - this could be a list
32906                self.write_keyword("IN");
32907                self.write(" (");
32908                self.generate_partition_bound_values(this)?;
32909                self.write(")");
32910            }
32911        } else if let (Some(from), Some(to)) = (&e.from_expressions, &e.to_expressions) {
32912            // FROM (from_expressions) TO (to_expressions)
32913            self.write_keyword("FROM");
32914            self.write(" (");
32915            self.generate_partition_bound_values(from)?;
32916            self.write(") ");
32917            self.write_keyword("TO");
32918            self.write(" (");
32919            self.generate_partition_bound_values(to)?;
32920            self.write(")");
32921        }
32922        Ok(())
32923    }
32924
32925    /// Generate partition bound values - handles Tuple expressions by outputting
32926    /// contents without wrapping parens (since caller provides the parens)
32927    fn generate_partition_bound_values(&mut self, expr: &Expression) -> Result<()> {
32928        if let Expression::Tuple(t) = expr {
32929            for (i, e) in t.expressions.iter().enumerate() {
32930                if i > 0 {
32931                    self.write(", ");
32932                }
32933                self.generate_expression(e)?;
32934            }
32935            Ok(())
32936        } else {
32937            self.generate_expression(expr)
32938        }
32939    }
32940
32941    fn generate_partition_by_list_property(&mut self, e: &PartitionByListProperty) -> Result<()> {
32942        // PARTITION BY LIST (partition_expressions) (create_expressions)
32943        self.write_keyword("PARTITION BY LIST");
32944        if let Some(partition_exprs) = &e.partition_expressions {
32945            self.write(" (");
32946            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
32947            self.generate_doris_partition_expressions(partition_exprs)?;
32948            self.write(")");
32949        }
32950        if let Some(create_exprs) = &e.create_expressions {
32951            self.write(" (");
32952            // Unwrap Tuple for partition definitions
32953            self.generate_doris_partition_definitions(create_exprs)?;
32954            self.write(")");
32955        }
32956        Ok(())
32957    }
32958
32959    fn generate_partition_by_range_property(&mut self, e: &PartitionByRangeProperty) -> Result<()> {
32960        // PARTITION BY RANGE (partition_expressions) (create_expressions)
32961        self.write_keyword("PARTITION BY RANGE");
32962        if let Some(partition_exprs) = &e.partition_expressions {
32963            self.write(" (");
32964            // Unwrap Tuple for partition columns (don't generate outer parens from Tuple)
32965            self.generate_doris_partition_expressions(partition_exprs)?;
32966            self.write(")");
32967        }
32968        if let Some(create_exprs) = &e.create_expressions {
32969            self.write(" (");
32970            // Check for dynamic partition (PartitionByRangePropertyDynamic) or static (Tuple of Partition)
32971            self.generate_doris_partition_definitions(create_exprs)?;
32972            self.write(")");
32973        }
32974        Ok(())
32975    }
32976
32977    /// Generate Doris partition column expressions (unwrap Tuple)
32978    fn generate_doris_partition_expressions(&mut self, expr: &Expression) -> Result<()> {
32979        if let Expression::Tuple(t) = expr {
32980            for (i, e) in t.expressions.iter().enumerate() {
32981                if i > 0 {
32982                    self.write(", ");
32983                }
32984                self.generate_expression(e)?;
32985            }
32986        } else {
32987            self.generate_expression(expr)?;
32988        }
32989        Ok(())
32990    }
32991
32992    /// Generate Doris partition definitions (comma-separated Partition expressions)
32993    fn generate_doris_partition_definitions(&mut self, expr: &Expression) -> Result<()> {
32994        match expr {
32995            Expression::Tuple(t) => {
32996                // Multiple partitions, comma-separated
32997                for (i, part) in t.expressions.iter().enumerate() {
32998                    if i > 0 {
32999                        self.write(", ");
33000                    }
33001                    // For Partition expressions, generate the inner PartitionRange/PartitionList directly
33002                    if let Expression::Partition(p) = part {
33003                        for (j, inner) in p.expressions.iter().enumerate() {
33004                            if j > 0 {
33005                                self.write(", ");
33006                            }
33007                            self.generate_expression(inner)?;
33008                        }
33009                    } else {
33010                        self.generate_expression(part)?;
33011                    }
33012                }
33013            }
33014            Expression::PartitionByRangePropertyDynamic(_) => {
33015                // Dynamic partition - FROM/TO/INTERVAL
33016                self.generate_expression(expr)?;
33017            }
33018            _ => {
33019                self.generate_expression(expr)?;
33020            }
33021        }
33022        Ok(())
33023    }
33024
33025    fn generate_partition_by_range_property_dynamic(
33026        &mut self,
33027        e: &PartitionByRangePropertyDynamic,
33028    ) -> Result<()> {
33029        if e.use_start_end {
33030            // StarRocks: START ('val') END ('val') EVERY (expr)
33031            if let Some(start) = &e.start {
33032                self.write_keyword("START");
33033                self.write(" (");
33034                self.generate_expression(start)?;
33035                self.write(")");
33036            }
33037            if let Some(end) = &e.end {
33038                self.write_space();
33039                self.write_keyword("END");
33040                self.write(" (");
33041                self.generate_expression(end)?;
33042                self.write(")");
33043            }
33044            if let Some(every) = &e.every {
33045                self.write_space();
33046                self.write_keyword("EVERY");
33047                self.write(" (");
33048                // Use unquoted interval format for StarRocks
33049                self.generate_doris_interval(every)?;
33050                self.write(")");
33051            }
33052        } else {
33053            // Doris: FROM (start) TO (end) INTERVAL n UNIT
33054            if let Some(start) = &e.start {
33055                self.write_keyword("FROM");
33056                self.write(" (");
33057                self.generate_expression(start)?;
33058                self.write(")");
33059            }
33060            if let Some(end) = &e.end {
33061                self.write_space();
33062                self.write_keyword("TO");
33063                self.write(" (");
33064                self.generate_expression(end)?;
33065                self.write(")");
33066            }
33067            if let Some(every) = &e.every {
33068                self.write_space();
33069                // Generate INTERVAL n UNIT (not quoted, for Doris dynamic partition)
33070                self.generate_doris_interval(every)?;
33071            }
33072        }
33073        Ok(())
33074    }
33075
33076    /// Generate Doris-style interval without quoting numbers: INTERVAL n UNIT
33077    fn generate_doris_interval(&mut self, expr: &Expression) -> Result<()> {
33078        if let Expression::Interval(interval) = expr {
33079            self.write_keyword("INTERVAL");
33080            if let Some(ref value) = interval.this {
33081                self.write_space();
33082                // If the value is a string literal that looks like a number,
33083                // output it without quotes (matching Python sqlglot's
33084                // partitionbyrangepropertydynamic_sql which converts back to number)
33085                match value {
33086                    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()) => {
33087                        if let Literal::String(s) = lit.as_ref() {
33088                            self.write(s);
33089                        }
33090                    }
33091                    _ => {
33092                        self.generate_expression(value)?;
33093                    }
33094                }
33095            }
33096            if let Some(ref unit_spec) = interval.unit {
33097                self.write_space();
33098                self.write_interval_unit_spec(unit_spec)?;
33099            }
33100            Ok(())
33101        } else {
33102            self.generate_expression(expr)
33103        }
33104    }
33105
33106    fn generate_partition_by_truncate(&mut self, e: &PartitionByTruncate) -> Result<()> {
33107        // TRUNCATE(expression, this)
33108        self.write_keyword("TRUNCATE");
33109        self.write("(");
33110        self.generate_expression(&e.expression)?;
33111        self.write(", ");
33112        self.generate_expression(&e.this)?;
33113        self.write(")");
33114        Ok(())
33115    }
33116
33117    fn generate_partition_list(&mut self, e: &PartitionList) -> Result<()> {
33118        // Doris: PARTITION name VALUES IN (val1, val2)
33119        self.write_keyword("PARTITION");
33120        self.write_space();
33121        self.generate_expression(&e.this)?;
33122        self.write_space();
33123        self.write_keyword("VALUES IN");
33124        self.write(" (");
33125        for (i, expr) in e.expressions.iter().enumerate() {
33126            if i > 0 {
33127                self.write(", ");
33128            }
33129            self.generate_expression(expr)?;
33130        }
33131        self.write(")");
33132        Ok(())
33133    }
33134
33135    fn generate_partition_range(&mut self, e: &PartitionRange) -> Result<()> {
33136        // Check if this is a TSQL-style simple range (e.g., "2 TO 5")
33137        // TSQL ranges have no expressions and just use `this TO expression`
33138        if e.expressions.is_empty() && e.expression.is_some() {
33139            // TSQL: simple range like "2 TO 5" - no PARTITION keyword
33140            self.generate_expression(&e.this)?;
33141            self.write_space();
33142            self.write_keyword("TO");
33143            self.write_space();
33144            self.generate_expression(e.expression.as_ref().unwrap())?;
33145            return Ok(());
33146        }
33147
33148        // Doris: PARTITION name VALUES LESS THAN (val) or PARTITION name VALUES [(val1), (val2))
33149        self.write_keyword("PARTITION");
33150        self.write_space();
33151        self.generate_expression(&e.this)?;
33152        self.write_space();
33153
33154        // Check if expressions contain Tuple (bracket notation) or single values (LESS THAN)
33155        if e.expressions.len() == 1 {
33156            // Single value: VALUES LESS THAN (val)
33157            self.write_keyword("VALUES LESS THAN");
33158            self.write(" (");
33159            self.generate_expression(&e.expressions[0])?;
33160            self.write(")");
33161        } else if !e.expressions.is_empty() {
33162            // Multiple values with Tuple: VALUES [(val1), (val2))
33163            self.write_keyword("VALUES");
33164            self.write(" [");
33165            for (i, expr) in e.expressions.iter().enumerate() {
33166                if i > 0 {
33167                    self.write(", ");
33168                }
33169                // If the expr is a Tuple, generate its contents wrapped in parens
33170                if let Expression::Tuple(t) = expr {
33171                    self.write("(");
33172                    for (j, inner) in t.expressions.iter().enumerate() {
33173                        if j > 0 {
33174                            self.write(", ");
33175                        }
33176                        self.generate_expression(inner)?;
33177                    }
33178                    self.write(")");
33179                } else {
33180                    self.write("(");
33181                    self.generate_expression(expr)?;
33182                    self.write(")");
33183                }
33184            }
33185            self.write(")");
33186        }
33187        Ok(())
33188    }
33189
33190    fn generate_partitioned_by_bucket(&mut self, e: &PartitionedByBucket) -> Result<()> {
33191        // BUCKET(this, expression)
33192        self.write_keyword("BUCKET");
33193        self.write("(");
33194        self.generate_expression(&e.this)?;
33195        self.write(", ");
33196        self.generate_expression(&e.expression)?;
33197        self.write(")");
33198        Ok(())
33199    }
33200
33201    fn generate_partition_by_property(&mut self, e: &PartitionByProperty) -> Result<()> {
33202        // BigQuery table property: PARTITION BY expression [, expression ...]
33203        self.write_keyword("PARTITION BY");
33204        self.write_space();
33205        for (i, expr) in e.expressions.iter().enumerate() {
33206            if i > 0 {
33207                self.write(", ");
33208            }
33209            self.generate_expression(expr)?;
33210        }
33211        Ok(())
33212    }
33213
33214    fn generate_partitioned_by_property(&mut self, e: &PartitionedByProperty) -> Result<()> {
33215        // PARTITIONED BY this (Teradata/ClickHouse use PARTITION BY)
33216        if matches!(
33217            self.config.dialect,
33218            Some(crate::dialects::DialectType::Teradata)
33219                | Some(crate::dialects::DialectType::ClickHouse)
33220        ) {
33221            self.write_keyword("PARTITION BY");
33222        } else {
33223            self.write_keyword("PARTITIONED BY");
33224        }
33225        self.write_space();
33226        // In pretty mode, always use multiline tuple format for PARTITIONED BY
33227        if self.config.pretty {
33228            if let Expression::Tuple(ref tuple) = *e.this {
33229                self.write("(");
33230                self.write_newline();
33231                self.indent_level += 1;
33232                for (i, expr) in tuple.expressions.iter().enumerate() {
33233                    if i > 0 {
33234                        self.write(",");
33235                        self.write_newline();
33236                    }
33237                    self.write_indent();
33238                    self.generate_expression(expr)?;
33239                }
33240                self.indent_level -= 1;
33241                self.write_newline();
33242                self.write(")");
33243            } else {
33244                self.generate_expression(&e.this)?;
33245            }
33246        } else {
33247            self.generate_expression(&e.this)?;
33248        }
33249        Ok(())
33250    }
33251
33252    fn generate_partitioned_of_property(&mut self, e: &PartitionedOfProperty) -> Result<()> {
33253        // PARTITION OF this FOR VALUES expression or PARTITION OF this DEFAULT
33254        self.write_keyword("PARTITION OF");
33255        self.write_space();
33256        self.generate_expression(&e.this)?;
33257        // Check if expression is a PartitionBoundSpec
33258        if let Expression::PartitionBoundSpec(_) = e.expression.as_ref() {
33259            self.write_space();
33260            self.write_keyword("FOR VALUES");
33261            self.write_space();
33262            self.generate_expression(&e.expression)?;
33263        } else {
33264            self.write_space();
33265            self.write_keyword("DEFAULT");
33266        }
33267        Ok(())
33268    }
33269
33270    fn generate_period_for_system_time_constraint(
33271        &mut self,
33272        e: &PeriodForSystemTimeConstraint,
33273    ) -> Result<()> {
33274        // PERIOD FOR SYSTEM_TIME (this, expression)
33275        self.write_keyword("PERIOD FOR SYSTEM_TIME");
33276        self.write(" (");
33277        self.generate_expression(&e.this)?;
33278        self.write(", ");
33279        self.generate_expression(&e.expression)?;
33280        self.write(")");
33281        Ok(())
33282    }
33283
33284    fn generate_pivot_alias(&mut self, e: &PivotAlias) -> Result<()> {
33285        // value AS alias
33286        // The alias can be an identifier or an expression (e.g., string concatenation)
33287        self.generate_expression(&e.this)?;
33288        self.write_space();
33289        self.write_keyword("AS");
33290        self.write_space();
33291        // When target dialect uses identifiers for UNPIVOT aliases, convert literals to identifiers
33292        if self.config.unpivot_aliases_are_identifiers {
33293            match &e.alias {
33294                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
33295                    let Literal::String(s) = lit.as_ref() else {
33296                        unreachable!()
33297                    };
33298                    // Convert string literal to identifier
33299                    self.generate_identifier(&Identifier::new(s.clone()))?;
33300                }
33301                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
33302                    let Literal::Number(n) = lit.as_ref() else {
33303                        unreachable!()
33304                    };
33305                    // Convert number literal to quoted identifier
33306                    let mut id = Identifier::new(n.clone());
33307                    id.quoted = true;
33308                    self.generate_identifier(&id)?;
33309                }
33310                other => {
33311                    self.generate_expression(other)?;
33312                }
33313            }
33314        } else {
33315            self.generate_expression(&e.alias)?;
33316        }
33317        Ok(())
33318    }
33319
33320    fn generate_pivot_any(&mut self, e: &PivotAny) -> Result<()> {
33321        // ANY or ANY [expression]
33322        self.write_keyword("ANY");
33323        if let Some(this) = &e.this {
33324            self.write_space();
33325            self.generate_expression(this)?;
33326        }
33327        Ok(())
33328    }
33329
33330    fn generate_predict(&mut self, e: &Predict) -> Result<()> {
33331        // ML.PREDICT(MODEL this, expression, [params_struct])
33332        self.write_keyword("ML.PREDICT");
33333        self.write("(");
33334        self.write_keyword("MODEL");
33335        self.write_space();
33336        self.generate_expression(&e.this)?;
33337        self.write(", ");
33338        self.generate_expression(&e.expression)?;
33339        if let Some(params) = &e.params_struct {
33340            self.write(", ");
33341            self.generate_expression(params)?;
33342        }
33343        self.write(")");
33344        Ok(())
33345    }
33346
33347    fn generate_previous_day(&mut self, e: &PreviousDay) -> Result<()> {
33348        // PREVIOUS_DAY(this, expression)
33349        self.write_keyword("PREVIOUS_DAY");
33350        self.write("(");
33351        self.generate_expression(&e.this)?;
33352        self.write(", ");
33353        self.generate_expression(&e.expression)?;
33354        self.write(")");
33355        Ok(())
33356    }
33357
33358    fn generate_primary_key(&mut self, e: &PrimaryKey) -> Result<()> {
33359        // PRIMARY KEY [name] (columns) [INCLUDE (...)] [options]
33360        self.write_keyword("PRIMARY KEY");
33361        if let Some(name) = &e.this {
33362            self.write_space();
33363            self.generate_expression(name)?;
33364        }
33365        if !e.expressions.is_empty() {
33366            self.write(" (");
33367            for (i, expr) in e.expressions.iter().enumerate() {
33368                if i > 0 {
33369                    self.write(", ");
33370                }
33371                self.generate_expression(expr)?;
33372            }
33373            self.write(")");
33374        }
33375        if let Some(include) = &e.include {
33376            self.write_space();
33377            self.generate_expression(include)?;
33378        }
33379        if !e.options.is_empty() {
33380            self.write_space();
33381            for (i, opt) in e.options.iter().enumerate() {
33382                if i > 0 {
33383                    self.write_space();
33384                }
33385                self.generate_expression(opt)?;
33386            }
33387        }
33388        Ok(())
33389    }
33390
33391    fn generate_primary_key_column_constraint(
33392        &mut self,
33393        _e: &PrimaryKeyColumnConstraint,
33394    ) -> Result<()> {
33395        // PRIMARY KEY constraint at column level
33396        self.write_keyword("PRIMARY KEY");
33397        Ok(())
33398    }
33399
33400    fn generate_path_column_constraint(&mut self, e: &PathColumnConstraint) -> Result<()> {
33401        // PATH 'xpath' constraint for XMLTABLE/JSON_TABLE columns
33402        self.write_keyword("PATH");
33403        self.write_space();
33404        self.generate_expression(&e.this)?;
33405        Ok(())
33406    }
33407
33408    fn generate_projection_def(&mut self, e: &ProjectionDef) -> Result<()> {
33409        // PROJECTION this (expression)
33410        self.write_keyword("PROJECTION");
33411        self.write_space();
33412        self.generate_expression(&e.this)?;
33413        self.write(" (");
33414        self.generate_expression(&e.expression)?;
33415        self.write(")");
33416        Ok(())
33417    }
33418
33419    fn generate_properties(&mut self, e: &Properties) -> Result<()> {
33420        // Properties list
33421        for (i, prop) in e.expressions.iter().enumerate() {
33422            if i > 0 {
33423                self.write(", ");
33424            }
33425            self.generate_expression(prop)?;
33426        }
33427        Ok(())
33428    }
33429
33430    fn generate_property(&mut self, e: &Property) -> Result<()> {
33431        // name=value
33432        self.generate_expression(&e.this)?;
33433        if let Some(value) = &e.value {
33434            self.write("=");
33435            self.generate_expression(value)?;
33436        }
33437        Ok(())
33438    }
33439
33440    fn generate_options_property(&mut self, e: &OptionsProperty) -> Result<()> {
33441        self.write_keyword("OPTIONS");
33442        if e.entries.is_empty() {
33443            self.write(" ()");
33444            return Ok(());
33445        }
33446
33447        if self.config.pretty {
33448            self.write(" (");
33449            self.write_newline();
33450            self.indent_level += 1;
33451            for (i, entry) in e.entries.iter().enumerate() {
33452                if i > 0 {
33453                    self.write(",");
33454                    self.write_newline();
33455                }
33456                self.write_indent();
33457                self.generate_identifier(&entry.key)?;
33458                self.write("=");
33459                self.generate_expression(&entry.value)?;
33460            }
33461            self.indent_level -= 1;
33462            self.write_newline();
33463            self.write(")");
33464        } else {
33465            self.write(" (");
33466            for (i, entry) in e.entries.iter().enumerate() {
33467                if i > 0 {
33468                    self.write(", ");
33469                }
33470                self.generate_identifier(&entry.key)?;
33471                self.write("=");
33472                self.generate_expression(&entry.value)?;
33473            }
33474            self.write(")");
33475        }
33476        Ok(())
33477    }
33478
33479    /// Generate BigQuery-style OPTIONS clause: OPTIONS (key=value, key=value, ...)
33480    fn generate_options_clause(&mut self, options: &[Expression]) -> Result<()> {
33481        self.write_keyword("OPTIONS");
33482        self.write(" (");
33483        for (i, opt) in options.iter().enumerate() {
33484            if i > 0 {
33485                self.write(", ");
33486            }
33487            self.generate_option_expression(opt)?;
33488        }
33489        self.write(")");
33490        Ok(())
33491    }
33492
33493    /// Generate Doris/StarRocks-style PROPERTIES clause: PROPERTIES ('key'='value', 'key'='value', ...)
33494    fn generate_properties_clause(&mut self, properties: &[Expression]) -> Result<()> {
33495        self.write_keyword("PROPERTIES");
33496        self.write(" (");
33497        for (i, prop) in properties.iter().enumerate() {
33498            if i > 0 {
33499                self.write(", ");
33500            }
33501            self.generate_option_expression(prop)?;
33502        }
33503        self.write(")");
33504        Ok(())
33505    }
33506
33507    /// Generate Databricks-style ENVIRONMENT clause: ENVIRONMENT (key = 'value', key = 'value', ...)
33508    fn generate_environment_clause(&mut self, environment: &[Expression]) -> Result<()> {
33509        self.write_keyword("ENVIRONMENT");
33510        self.write(" (");
33511        for (i, env_item) in environment.iter().enumerate() {
33512            if i > 0 {
33513                self.write(", ");
33514            }
33515            self.generate_environment_expression(env_item)?;
33516        }
33517        self.write(")");
33518        Ok(())
33519    }
33520
33521    /// Generate an environment expression with spaces around =
33522    fn generate_environment_expression(&mut self, expr: &Expression) -> Result<()> {
33523        match expr {
33524            Expression::Eq(eq) => {
33525                // Generate key = value with spaces (Databricks ENVIRONMENT style)
33526                self.generate_expression(&eq.left)?;
33527                self.write(" = ");
33528                self.generate_expression(&eq.right)?;
33529                Ok(())
33530            }
33531            _ => self.generate_expression(expr),
33532        }
33533    }
33534
33535    /// Generate Hive-style TBLPROPERTIES clause: TBLPROPERTIES ('key'='value', ...)
33536    fn generate_tblproperties_clause(&mut self, options: &[Expression]) -> Result<()> {
33537        self.write_keyword("TBLPROPERTIES");
33538        if self.config.pretty {
33539            self.write(" (");
33540            self.write_newline();
33541            self.indent_level += 1;
33542            for (i, opt) in options.iter().enumerate() {
33543                if i > 0 {
33544                    self.write(",");
33545                    self.write_newline();
33546                }
33547                self.write_indent();
33548                self.generate_option_expression(opt)?;
33549            }
33550            self.indent_level -= 1;
33551            self.write_newline();
33552            self.write(")");
33553        } else {
33554            self.write(" (");
33555            for (i, opt) in options.iter().enumerate() {
33556                if i > 0 {
33557                    self.write(", ");
33558                }
33559                self.generate_option_expression(opt)?;
33560            }
33561            self.write(")");
33562        }
33563        Ok(())
33564    }
33565
33566    /// Generate an option expression without spaces around =
33567    fn generate_option_expression(&mut self, expr: &Expression) -> Result<()> {
33568        match expr {
33569            Expression::Eq(eq) => {
33570                // Generate key=value without spaces
33571                self.generate_expression(&eq.left)?;
33572                self.write("=");
33573                self.generate_expression(&eq.right)?;
33574                Ok(())
33575            }
33576            _ => self.generate_expression(expr),
33577        }
33578    }
33579
33580    fn generate_pseudo_type(&mut self, e: &PseudoType) -> Result<()> {
33581        // Just output the name
33582        self.generate_expression(&e.this)?;
33583        Ok(())
33584    }
33585
33586    fn generate_put(&mut self, e: &PutStmt) -> Result<()> {
33587        // PUT source_file @stage [options]
33588        self.write_keyword("PUT");
33589        self.write_space();
33590
33591        // Source file path - preserve original quoting
33592        if e.source_quoted {
33593            self.write("'");
33594            self.write(&e.source);
33595            self.write("'");
33596        } else {
33597            self.write(&e.source);
33598        }
33599
33600        self.write_space();
33601
33602        // Target stage reference - output the string directly (includes @)
33603        if let Expression::Literal(lit) = &e.target {
33604            if let Literal::String(s) = lit.as_ref() {
33605                self.write(s);
33606            }
33607        } else {
33608            self.generate_expression(&e.target)?;
33609        }
33610
33611        // Optional parameters: KEY=VALUE
33612        for param in &e.params {
33613            self.write_space();
33614            self.write(&param.name);
33615            if let Some(ref value) = param.value {
33616                self.write("=");
33617                self.generate_expression(value)?;
33618            }
33619        }
33620
33621        Ok(())
33622    }
33623
33624    fn generate_quantile(&mut self, e: &Quantile) -> Result<()> {
33625        // QUANTILE(this, quantile)
33626        self.write_keyword("QUANTILE");
33627        self.write("(");
33628        self.generate_expression(&e.this)?;
33629        if let Some(quantile) = &e.quantile {
33630            self.write(", ");
33631            self.generate_expression(quantile)?;
33632        }
33633        self.write(")");
33634        Ok(())
33635    }
33636
33637    fn generate_query_band(&mut self, e: &QueryBand) -> Result<()> {
33638        // QUERY_BAND = this [UPDATE] [FOR scope]
33639        if matches!(
33640            self.config.dialect,
33641            Some(crate::dialects::DialectType::Teradata)
33642        ) {
33643            self.write_keyword("SET");
33644            self.write_space();
33645        }
33646        self.write_keyword("QUERY_BAND");
33647        self.write(" = ");
33648        self.generate_expression(&e.this)?;
33649        if e.update.is_some() {
33650            self.write_space();
33651            self.write_keyword("UPDATE");
33652        }
33653        if let Some(scope) = &e.scope {
33654            self.write_space();
33655            self.write_keyword("FOR");
33656            self.write_space();
33657            self.generate_expression(scope)?;
33658        }
33659        Ok(())
33660    }
33661
33662    fn generate_query_option(&mut self, e: &QueryOption) -> Result<()> {
33663        // this = expression
33664        self.generate_expression(&e.this)?;
33665        if let Some(expression) = &e.expression {
33666            self.write(" = ");
33667            self.generate_expression(expression)?;
33668        }
33669        Ok(())
33670    }
33671
33672    fn generate_query_transform(&mut self, e: &QueryTransform) -> Result<()> {
33673        // TRANSFORM (expressions) [row_format_before] [RECORDWRITER record_writer] USING command_script [AS schema] [row_format_after] [RECORDREADER record_reader]
33674        self.write_keyword("TRANSFORM");
33675        self.write("(");
33676        for (i, expr) in e.expressions.iter().enumerate() {
33677            if i > 0 {
33678                self.write(", ");
33679            }
33680            self.generate_expression(expr)?;
33681        }
33682        self.write(")");
33683        if let Some(row_format_before) = &e.row_format_before {
33684            self.write_space();
33685            self.generate_expression(row_format_before)?;
33686        }
33687        if let Some(record_writer) = &e.record_writer {
33688            self.write_space();
33689            self.write_keyword("RECORDWRITER");
33690            self.write_space();
33691            self.generate_expression(record_writer)?;
33692        }
33693        if let Some(command_script) = &e.command_script {
33694            self.write_space();
33695            self.write_keyword("USING");
33696            self.write_space();
33697            self.generate_expression(command_script)?;
33698        }
33699        if let Some(schema) = &e.schema {
33700            self.write_space();
33701            self.write_keyword("AS");
33702            self.write_space();
33703            self.generate_expression(schema)?;
33704        }
33705        if let Some(row_format_after) = &e.row_format_after {
33706            self.write_space();
33707            self.generate_expression(row_format_after)?;
33708        }
33709        if let Some(record_reader) = &e.record_reader {
33710            self.write_space();
33711            self.write_keyword("RECORDREADER");
33712            self.write_space();
33713            self.generate_expression(record_reader)?;
33714        }
33715        Ok(())
33716    }
33717
33718    fn generate_randn(&mut self, e: &Randn) -> Result<()> {
33719        // RANDN([seed])
33720        self.write_keyword("RANDN");
33721        self.write("(");
33722        if let Some(this) = &e.this {
33723            self.generate_expression(this)?;
33724        }
33725        self.write(")");
33726        Ok(())
33727    }
33728
33729    fn generate_randstr(&mut self, e: &Randstr) -> Result<()> {
33730        // RANDSTR(this, [generator])
33731        self.write_keyword("RANDSTR");
33732        self.write("(");
33733        self.generate_expression(&e.this)?;
33734        if let Some(generator) = &e.generator {
33735            self.write(", ");
33736            self.generate_expression(generator)?;
33737        }
33738        self.write(")");
33739        Ok(())
33740    }
33741
33742    fn generate_range_bucket(&mut self, e: &RangeBucket) -> Result<()> {
33743        // RANGE_BUCKET(this, expression)
33744        self.write_keyword("RANGE_BUCKET");
33745        self.write("(");
33746        self.generate_expression(&e.this)?;
33747        self.write(", ");
33748        self.generate_expression(&e.expression)?;
33749        self.write(")");
33750        Ok(())
33751    }
33752
33753    fn generate_range_n(&mut self, e: &RangeN) -> Result<()> {
33754        // RANGE_N(this BETWEEN expressions [EACH each])
33755        self.write_keyword("RANGE_N");
33756        self.write("(");
33757        self.generate_expression(&e.this)?;
33758        self.write_space();
33759        self.write_keyword("BETWEEN");
33760        self.write_space();
33761        for (i, expr) in e.expressions.iter().enumerate() {
33762            if i > 0 {
33763                self.write(", ");
33764            }
33765            self.generate_expression(expr)?;
33766        }
33767        if let Some(each) = &e.each {
33768            self.write_space();
33769            self.write_keyword("EACH");
33770            self.write_space();
33771            self.generate_expression(each)?;
33772        }
33773        self.write(")");
33774        Ok(())
33775    }
33776
33777    fn generate_read_csv(&mut self, e: &ReadCSV) -> Result<()> {
33778        // READ_CSV(this, expressions...)
33779        self.write_keyword("READ_CSV");
33780        self.write("(");
33781        self.generate_expression(&e.this)?;
33782        for expr in &e.expressions {
33783            self.write(", ");
33784            self.generate_expression(expr)?;
33785        }
33786        self.write(")");
33787        Ok(())
33788    }
33789
33790    fn generate_read_parquet(&mut self, e: &ReadParquet) -> Result<()> {
33791        // READ_PARQUET(expressions...)
33792        self.write_keyword("READ_PARQUET");
33793        self.write("(");
33794        for (i, expr) in e.expressions.iter().enumerate() {
33795            if i > 0 {
33796                self.write(", ");
33797            }
33798            self.generate_expression(expr)?;
33799        }
33800        self.write(")");
33801        Ok(())
33802    }
33803
33804    fn generate_recursive_with_search(&mut self, e: &RecursiveWithSearch) -> Result<()> {
33805        // SEARCH kind FIRST BY this SET expression [USING using]
33806        // or CYCLE this SET expression [USING using]
33807        if e.kind == "CYCLE" {
33808            self.write_keyword("CYCLE");
33809        } else {
33810            self.write_keyword("SEARCH");
33811            self.write_space();
33812            self.write(&e.kind);
33813            self.write_space();
33814            self.write_keyword("FIRST BY");
33815        }
33816        self.write_space();
33817        self.generate_expression(&e.this)?;
33818        self.write_space();
33819        self.write_keyword("SET");
33820        self.write_space();
33821        self.generate_expression(&e.expression)?;
33822        if let Some(using) = &e.using {
33823            self.write_space();
33824            self.write_keyword("USING");
33825            self.write_space();
33826            self.generate_expression(using)?;
33827        }
33828        Ok(())
33829    }
33830
33831    fn generate_reduce(&mut self, e: &Reduce) -> Result<()> {
33832        // REDUCE(this, initial, merge, [finish])
33833        self.write_keyword("REDUCE");
33834        self.write("(");
33835        self.generate_expression(&e.this)?;
33836        if let Some(initial) = &e.initial {
33837            self.write(", ");
33838            self.generate_expression(initial)?;
33839        }
33840        if let Some(merge) = &e.merge {
33841            self.write(", ");
33842            self.generate_expression(merge)?;
33843        }
33844        if let Some(finish) = &e.finish {
33845            self.write(", ");
33846            self.generate_expression(finish)?;
33847        }
33848        self.write(")");
33849        Ok(())
33850    }
33851
33852    fn generate_reference(&mut self, e: &Reference) -> Result<()> {
33853        // REFERENCES this (expressions) [options]
33854        self.write_keyword("REFERENCES");
33855        self.write_space();
33856        self.generate_expression(&e.this)?;
33857        if !e.expressions.is_empty() {
33858            self.write(" (");
33859            for (i, expr) in e.expressions.iter().enumerate() {
33860                if i > 0 {
33861                    self.write(", ");
33862                }
33863                self.generate_expression(expr)?;
33864            }
33865            self.write(")");
33866        }
33867        for opt in &e.options {
33868            self.write_space();
33869            self.generate_expression(opt)?;
33870        }
33871        Ok(())
33872    }
33873
33874    fn generate_refresh(&mut self, e: &Refresh) -> Result<()> {
33875        // REFRESH [kind] this
33876        self.write_keyword("REFRESH");
33877        if !e.kind.is_empty() {
33878            self.write_space();
33879            self.write_keyword(&e.kind);
33880        }
33881        self.write_space();
33882        self.generate_expression(&e.this)?;
33883        Ok(())
33884    }
33885
33886    fn generate_refresh_trigger_property(&mut self, e: &RefreshTriggerProperty) -> Result<()> {
33887        // Doris REFRESH clause: REFRESH method ON kind [EVERY n UNIT] [STARTS 'datetime']
33888        self.write_keyword("REFRESH");
33889        self.write_space();
33890        self.write_keyword(&e.method);
33891
33892        if let Some(ref kind) = e.kind {
33893            self.write_space();
33894            self.write_keyword("ON");
33895            self.write_space();
33896            self.write_keyword(kind);
33897
33898            // EVERY n UNIT
33899            if let Some(ref every) = e.every {
33900                self.write_space();
33901                self.write_keyword("EVERY");
33902                self.write_space();
33903                self.generate_expression(every)?;
33904                if let Some(ref unit) = e.unit {
33905                    self.write_space();
33906                    self.write_keyword(unit);
33907                }
33908            }
33909
33910            // STARTS 'datetime'
33911            if let Some(ref starts) = e.starts {
33912                self.write_space();
33913                self.write_keyword("STARTS");
33914                self.write_space();
33915                self.generate_expression(starts)?;
33916            }
33917        }
33918        Ok(())
33919    }
33920
33921    fn generate_regexp_count(&mut self, e: &RegexpCount) -> Result<()> {
33922        // REGEXP_COUNT(this, expression, position, parameters)
33923        self.write_keyword("REGEXP_COUNT");
33924        self.write("(");
33925        self.generate_expression(&e.this)?;
33926        self.write(", ");
33927        self.generate_expression(&e.expression)?;
33928        if let Some(position) = &e.position {
33929            self.write(", ");
33930            self.generate_expression(position)?;
33931        }
33932        if let Some(parameters) = &e.parameters {
33933            self.write(", ");
33934            self.generate_expression(parameters)?;
33935        }
33936        self.write(")");
33937        Ok(())
33938    }
33939
33940    fn generate_regexp_extract_all(&mut self, e: &RegexpExtractAll) -> Result<()> {
33941        // REGEXP_EXTRACT_ALL(this, expression, group, parameters, position, occurrence)
33942        self.write_keyword("REGEXP_EXTRACT_ALL");
33943        self.write("(");
33944        self.generate_expression(&e.this)?;
33945        self.write(", ");
33946        self.generate_expression(&e.expression)?;
33947        if let Some(group) = &e.group {
33948            self.write(", ");
33949            self.generate_expression(group)?;
33950        }
33951        self.write(")");
33952        Ok(())
33953    }
33954
33955    fn generate_regexp_full_match(&mut self, e: &RegexpFullMatch) -> Result<()> {
33956        // REGEXP_FULL_MATCH(this, expression)
33957        self.write_keyword("REGEXP_FULL_MATCH");
33958        self.write("(");
33959        self.generate_expression(&e.this)?;
33960        self.write(", ");
33961        self.generate_expression(&e.expression)?;
33962        self.write(")");
33963        Ok(())
33964    }
33965
33966    fn generate_regexp_i_like(&mut self, e: &RegexpILike) -> Result<()> {
33967        use crate::dialects::DialectType;
33968        // PostgreSQL/Redshift uses ~* operator for case-insensitive regex matching
33969        if matches!(
33970            self.config.dialect,
33971            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift)
33972        ) && e.flag.is_none()
33973        {
33974            self.generate_expression(&e.this)?;
33975            self.write(" ~* ");
33976            self.generate_expression(&e.expression)?;
33977        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
33978            // Snowflake uses REGEXP_LIKE(x, pattern, 'i')
33979            self.write_keyword("REGEXP_LIKE");
33980            self.write("(");
33981            self.generate_expression(&e.this)?;
33982            self.write(", ");
33983            self.generate_expression(&e.expression)?;
33984            self.write(", ");
33985            if let Some(flag) = &e.flag {
33986                self.generate_expression(flag)?;
33987            } else {
33988                self.write("'i'");
33989            }
33990            self.write(")");
33991        } else {
33992            // this REGEXP_ILIKE expression or REGEXP_ILIKE(this, expression, flag)
33993            self.generate_expression(&e.this)?;
33994            self.write_space();
33995            self.write_keyword("REGEXP_ILIKE");
33996            self.write_space();
33997            self.generate_expression(&e.expression)?;
33998            if let Some(flag) = &e.flag {
33999                self.write(", ");
34000                self.generate_expression(flag)?;
34001            }
34002        }
34003        Ok(())
34004    }
34005
34006    fn generate_regexp_instr(&mut self, e: &RegexpInstr) -> Result<()> {
34007        // REGEXP_INSTR(this, expression, position, occurrence, option, parameters, group)
34008        self.write_keyword("REGEXP_INSTR");
34009        self.write("(");
34010        self.generate_expression(&e.this)?;
34011        self.write(", ");
34012        self.generate_expression(&e.expression)?;
34013        if let Some(position) = &e.position {
34014            self.write(", ");
34015            self.generate_expression(position)?;
34016        }
34017        if let Some(occurrence) = &e.occurrence {
34018            self.write(", ");
34019            self.generate_expression(occurrence)?;
34020        }
34021        if let Some(option) = &e.option {
34022            self.write(", ");
34023            self.generate_expression(option)?;
34024        }
34025        if let Some(parameters) = &e.parameters {
34026            self.write(", ");
34027            self.generate_expression(parameters)?;
34028        }
34029        if let Some(group) = &e.group {
34030            self.write(", ");
34031            self.generate_expression(group)?;
34032        }
34033        self.write(")");
34034        Ok(())
34035    }
34036
34037    fn generate_regexp_split(&mut self, e: &RegexpSplit) -> Result<()> {
34038        // REGEXP_SPLIT(this, expression, limit)
34039        self.write_keyword("REGEXP_SPLIT");
34040        self.write("(");
34041        self.generate_expression(&e.this)?;
34042        self.write(", ");
34043        self.generate_expression(&e.expression)?;
34044        if let Some(limit) = &e.limit {
34045            self.write(", ");
34046            self.generate_expression(limit)?;
34047        }
34048        self.write(")");
34049        Ok(())
34050    }
34051
34052    fn generate_regr_avgx(&mut self, e: &RegrAvgx) -> Result<()> {
34053        // REGR_AVGX(this, expression)
34054        self.write_keyword("REGR_AVGX");
34055        self.write("(");
34056        self.generate_expression(&e.this)?;
34057        self.write(", ");
34058        self.generate_expression(&e.expression)?;
34059        self.write(")");
34060        Ok(())
34061    }
34062
34063    fn generate_regr_avgy(&mut self, e: &RegrAvgy) -> Result<()> {
34064        // REGR_AVGY(this, expression)
34065        self.write_keyword("REGR_AVGY");
34066        self.write("(");
34067        self.generate_expression(&e.this)?;
34068        self.write(", ");
34069        self.generate_expression(&e.expression)?;
34070        self.write(")");
34071        Ok(())
34072    }
34073
34074    fn generate_regr_count(&mut self, e: &RegrCount) -> Result<()> {
34075        // REGR_COUNT(this, expression)
34076        self.write_keyword("REGR_COUNT");
34077        self.write("(");
34078        self.generate_expression(&e.this)?;
34079        self.write(", ");
34080        self.generate_expression(&e.expression)?;
34081        self.write(")");
34082        Ok(())
34083    }
34084
34085    fn generate_regr_intercept(&mut self, e: &RegrIntercept) -> Result<()> {
34086        // REGR_INTERCEPT(this, expression)
34087        self.write_keyword("REGR_INTERCEPT");
34088        self.write("(");
34089        self.generate_expression(&e.this)?;
34090        self.write(", ");
34091        self.generate_expression(&e.expression)?;
34092        self.write(")");
34093        Ok(())
34094    }
34095
34096    fn generate_regr_r2(&mut self, e: &RegrR2) -> Result<()> {
34097        // REGR_R2(this, expression)
34098        self.write_keyword("REGR_R2");
34099        self.write("(");
34100        self.generate_expression(&e.this)?;
34101        self.write(", ");
34102        self.generate_expression(&e.expression)?;
34103        self.write(")");
34104        Ok(())
34105    }
34106
34107    fn generate_regr_slope(&mut self, e: &RegrSlope) -> Result<()> {
34108        // REGR_SLOPE(this, expression)
34109        self.write_keyword("REGR_SLOPE");
34110        self.write("(");
34111        self.generate_expression(&e.this)?;
34112        self.write(", ");
34113        self.generate_expression(&e.expression)?;
34114        self.write(")");
34115        Ok(())
34116    }
34117
34118    fn generate_regr_sxx(&mut self, e: &RegrSxx) -> Result<()> {
34119        // REGR_SXX(this, expression)
34120        self.write_keyword("REGR_SXX");
34121        self.write("(");
34122        self.generate_expression(&e.this)?;
34123        self.write(", ");
34124        self.generate_expression(&e.expression)?;
34125        self.write(")");
34126        Ok(())
34127    }
34128
34129    fn generate_regr_sxy(&mut self, e: &RegrSxy) -> Result<()> {
34130        // REGR_SXY(this, expression)
34131        self.write_keyword("REGR_SXY");
34132        self.write("(");
34133        self.generate_expression(&e.this)?;
34134        self.write(", ");
34135        self.generate_expression(&e.expression)?;
34136        self.write(")");
34137        Ok(())
34138    }
34139
34140    fn generate_regr_syy(&mut self, e: &RegrSyy) -> Result<()> {
34141        // REGR_SYY(this, expression)
34142        self.write_keyword("REGR_SYY");
34143        self.write("(");
34144        self.generate_expression(&e.this)?;
34145        self.write(", ");
34146        self.generate_expression(&e.expression)?;
34147        self.write(")");
34148        Ok(())
34149    }
34150
34151    fn generate_regr_valx(&mut self, e: &RegrValx) -> Result<()> {
34152        // REGR_VALX(this, expression)
34153        self.write_keyword("REGR_VALX");
34154        self.write("(");
34155        self.generate_expression(&e.this)?;
34156        self.write(", ");
34157        self.generate_expression(&e.expression)?;
34158        self.write(")");
34159        Ok(())
34160    }
34161
34162    fn generate_regr_valy(&mut self, e: &RegrValy) -> Result<()> {
34163        // REGR_VALY(this, expression)
34164        self.write_keyword("REGR_VALY");
34165        self.write("(");
34166        self.generate_expression(&e.this)?;
34167        self.write(", ");
34168        self.generate_expression(&e.expression)?;
34169        self.write(")");
34170        Ok(())
34171    }
34172
34173    fn generate_remote_with_connection_model_property(
34174        &mut self,
34175        e: &RemoteWithConnectionModelProperty,
34176    ) -> Result<()> {
34177        // REMOTE WITH CONNECTION this
34178        self.write_keyword("REMOTE WITH CONNECTION");
34179        self.write_space();
34180        self.generate_expression(&e.this)?;
34181        Ok(())
34182    }
34183
34184    fn generate_rename_column(&mut self, e: &RenameColumn) -> Result<()> {
34185        // RENAME COLUMN [IF EXISTS] this TO new_name
34186        self.write_keyword("RENAME COLUMN");
34187        if e.exists {
34188            self.write_space();
34189            self.write_keyword("IF EXISTS");
34190        }
34191        self.write_space();
34192        self.generate_expression(&e.this)?;
34193        if let Some(to) = &e.to {
34194            self.write_space();
34195            self.write_keyword("TO");
34196            self.write_space();
34197            self.generate_expression(to)?;
34198        }
34199        Ok(())
34200    }
34201
34202    fn generate_replace_partition(&mut self, e: &ReplacePartition) -> Result<()> {
34203        // REPLACE PARTITION expression [FROM source]
34204        self.write_keyword("REPLACE PARTITION");
34205        self.write_space();
34206        self.generate_expression(&e.expression)?;
34207        if let Some(source) = &e.source {
34208            self.write_space();
34209            self.write_keyword("FROM");
34210            self.write_space();
34211            self.generate_expression(source)?;
34212        }
34213        Ok(())
34214    }
34215
34216    fn generate_returning(&mut self, e: &Returning) -> Result<()> {
34217        // RETURNING expressions [INTO into]
34218        // TSQL and Fabric use OUTPUT instead of RETURNING
34219        let keyword = match self.config.dialect {
34220            Some(DialectType::TSQL) | Some(DialectType::Fabric) => "OUTPUT",
34221            _ => "RETURNING",
34222        };
34223        self.write_keyword(keyword);
34224        self.write_space();
34225        for (i, expr) in e.expressions.iter().enumerate() {
34226            if i > 0 {
34227                self.write(", ");
34228            }
34229            self.generate_expression(expr)?;
34230        }
34231        if let Some(into) = &e.into {
34232            self.write_space();
34233            self.write_keyword("INTO");
34234            self.write_space();
34235            self.generate_expression(into)?;
34236        }
34237        Ok(())
34238    }
34239
34240    fn generate_output_clause(&mut self, output: &OutputClause) -> Result<()> {
34241        // OUTPUT expressions [INTO into_table]
34242        self.write_space();
34243        self.write_keyword("OUTPUT");
34244        self.write_space();
34245        for (i, expr) in output.columns.iter().enumerate() {
34246            if i > 0 {
34247                self.write(", ");
34248            }
34249            self.generate_expression(expr)?;
34250        }
34251        if let Some(into_table) = &output.into_table {
34252            self.write_space();
34253            self.write_keyword("INTO");
34254            self.write_space();
34255            self.generate_expression(into_table)?;
34256        }
34257        Ok(())
34258    }
34259
34260    fn generate_returns_property(&mut self, e: &ReturnsProperty) -> Result<()> {
34261        // RETURNS [TABLE] this [NULL ON NULL INPUT | CALLED ON NULL INPUT]
34262        self.write_keyword("RETURNS");
34263        if e.is_table.is_some() {
34264            self.write_space();
34265            self.write_keyword("TABLE");
34266        }
34267        if let Some(table) = &e.table {
34268            self.write_space();
34269            self.generate_expression(table)?;
34270        } else if let Some(this) = &e.this {
34271            self.write_space();
34272            self.generate_expression(this)?;
34273        }
34274        if e.null.is_some() {
34275            self.write_space();
34276            self.write_keyword("NULL ON NULL INPUT");
34277        }
34278        Ok(())
34279    }
34280
34281    fn generate_rollback(&mut self, e: &Rollback) -> Result<()> {
34282        // ROLLBACK [TRANSACTION [transaction_name]] [TO savepoint]
34283        self.write_keyword("ROLLBACK");
34284
34285        // TSQL always uses ROLLBACK TRANSACTION
34286        if e.this.is_none()
34287            && matches!(
34288                self.config.dialect,
34289                Some(DialectType::TSQL) | Some(DialectType::Fabric)
34290            )
34291        {
34292            self.write_space();
34293            self.write_keyword("TRANSACTION");
34294        }
34295
34296        // Check if this has TRANSACTION keyword or transaction name
34297        if let Some(this) = &e.this {
34298            // Check if it's just the "TRANSACTION" marker or an actual transaction name
34299            let is_transaction_marker = matches!(
34300                this.as_ref(),
34301                Expression::Identifier(id) if id.name == "TRANSACTION"
34302            );
34303
34304            self.write_space();
34305            self.write_keyword("TRANSACTION");
34306
34307            // If it's a real transaction name, output it
34308            if !is_transaction_marker {
34309                self.write_space();
34310                self.generate_expression(this)?;
34311            }
34312        }
34313
34314        // Output TO savepoint
34315        if let Some(savepoint) = &e.savepoint {
34316            self.write_space();
34317            self.write_keyword("TO");
34318            self.write_space();
34319            self.generate_expression(savepoint)?;
34320        }
34321        Ok(())
34322    }
34323
34324    fn generate_rollup(&mut self, e: &Rollup) -> Result<()> {
34325        // Python: return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP"
34326        if e.expressions.is_empty() {
34327            self.write_keyword("WITH ROLLUP");
34328        } else {
34329            self.write_keyword("ROLLUP");
34330            self.write("(");
34331            for (i, expr) in e.expressions.iter().enumerate() {
34332                if i > 0 {
34333                    self.write(", ");
34334                }
34335                self.generate_expression(expr)?;
34336            }
34337            self.write(")");
34338        }
34339        Ok(())
34340    }
34341
34342    fn generate_row_format_delimited_property(
34343        &mut self,
34344        e: &RowFormatDelimitedProperty,
34345    ) -> Result<()> {
34346        // ROW FORMAT DELIMITED [FIELDS TERMINATED BY ...] [ESCAPED BY ...] [COLLECTION ITEMS TERMINATED BY ...] [MAP KEYS TERMINATED BY ...] [LINES TERMINATED BY ...] [NULL DEFINED AS ...]
34347        self.write_keyword("ROW FORMAT DELIMITED");
34348        if let Some(fields) = &e.fields {
34349            self.write_space();
34350            self.write_keyword("FIELDS TERMINATED BY");
34351            self.write_space();
34352            self.generate_expression(fields)?;
34353        }
34354        if let Some(escaped) = &e.escaped {
34355            self.write_space();
34356            self.write_keyword("ESCAPED BY");
34357            self.write_space();
34358            self.generate_expression(escaped)?;
34359        }
34360        if let Some(items) = &e.collection_items {
34361            self.write_space();
34362            self.write_keyword("COLLECTION ITEMS TERMINATED BY");
34363            self.write_space();
34364            self.generate_expression(items)?;
34365        }
34366        if let Some(keys) = &e.map_keys {
34367            self.write_space();
34368            self.write_keyword("MAP KEYS TERMINATED BY");
34369            self.write_space();
34370            self.generate_expression(keys)?;
34371        }
34372        if let Some(lines) = &e.lines {
34373            self.write_space();
34374            self.write_keyword("LINES TERMINATED BY");
34375            self.write_space();
34376            self.generate_expression(lines)?;
34377        }
34378        if let Some(null) = &e.null {
34379            self.write_space();
34380            self.write_keyword("NULL DEFINED AS");
34381            self.write_space();
34382            self.generate_expression(null)?;
34383        }
34384        if let Some(serde) = &e.serde {
34385            self.write_space();
34386            self.generate_expression(serde)?;
34387        }
34388        Ok(())
34389    }
34390
34391    fn generate_row_format_property(&mut self, e: &RowFormatProperty) -> Result<()> {
34392        // ROW FORMAT this
34393        self.write_keyword("ROW FORMAT");
34394        self.write_space();
34395        self.generate_expression(&e.this)?;
34396        Ok(())
34397    }
34398
34399    fn generate_row_format_serde_property(&mut self, e: &RowFormatSerdeProperty) -> Result<()> {
34400        // ROW FORMAT SERDE this [WITH SERDEPROPERTIES (...)]
34401        self.write_keyword("ROW FORMAT SERDE");
34402        self.write_space();
34403        self.generate_expression(&e.this)?;
34404        if let Some(props) = &e.serde_properties {
34405            self.write_space();
34406            // SerdeProperties generates its own "[WITH] SERDEPROPERTIES (...)"
34407            self.generate_expression(props)?;
34408        }
34409        Ok(())
34410    }
34411
34412    fn generate_sha2(&mut self, e: &SHA2) -> Result<()> {
34413        // SHA2(this, length)
34414        self.write_keyword("SHA2");
34415        self.write("(");
34416        self.generate_expression(&e.this)?;
34417        if let Some(length) = e.length {
34418            self.write(", ");
34419            self.write(&length.to_string());
34420        }
34421        self.write(")");
34422        Ok(())
34423    }
34424
34425    fn generate_sha2_digest(&mut self, e: &SHA2Digest) -> Result<()> {
34426        // SHA2_DIGEST(this, length)
34427        self.write_keyword("SHA2_DIGEST");
34428        self.write("(");
34429        self.generate_expression(&e.this)?;
34430        if let Some(length) = e.length {
34431            self.write(", ");
34432            self.write(&length.to_string());
34433        }
34434        self.write(")");
34435        Ok(())
34436    }
34437
34438    fn generate_safe_add(&mut self, e: &SafeAdd) -> Result<()> {
34439        let name = if matches!(
34440            self.config.dialect,
34441            Some(crate::dialects::DialectType::Spark)
34442                | Some(crate::dialects::DialectType::Databricks)
34443        ) {
34444            "TRY_ADD"
34445        } else {
34446            "SAFE_ADD"
34447        };
34448        self.write_keyword(name);
34449        self.write("(");
34450        self.generate_expression(&e.this)?;
34451        self.write(", ");
34452        self.generate_expression(&e.expression)?;
34453        self.write(")");
34454        Ok(())
34455    }
34456
34457    fn generate_safe_divide(&mut self, e: &SafeDivide) -> Result<()> {
34458        // SAFE_DIVIDE(this, expression)
34459        self.write_keyword("SAFE_DIVIDE");
34460        self.write("(");
34461        self.generate_expression(&e.this)?;
34462        self.write(", ");
34463        self.generate_expression(&e.expression)?;
34464        self.write(")");
34465        Ok(())
34466    }
34467
34468    fn generate_safe_multiply(&mut self, e: &SafeMultiply) -> Result<()> {
34469        let name = if matches!(
34470            self.config.dialect,
34471            Some(crate::dialects::DialectType::Spark)
34472                | Some(crate::dialects::DialectType::Databricks)
34473        ) {
34474            "TRY_MULTIPLY"
34475        } else {
34476            "SAFE_MULTIPLY"
34477        };
34478        self.write_keyword(name);
34479        self.write("(");
34480        self.generate_expression(&e.this)?;
34481        self.write(", ");
34482        self.generate_expression(&e.expression)?;
34483        self.write(")");
34484        Ok(())
34485    }
34486
34487    fn generate_safe_subtract(&mut self, e: &SafeSubtract) -> Result<()> {
34488        let name = if matches!(
34489            self.config.dialect,
34490            Some(crate::dialects::DialectType::Spark)
34491                | Some(crate::dialects::DialectType::Databricks)
34492        ) {
34493            "TRY_SUBTRACT"
34494        } else {
34495            "SAFE_SUBTRACT"
34496        };
34497        self.write_keyword(name);
34498        self.write("(");
34499        self.generate_expression(&e.this)?;
34500        self.write(", ");
34501        self.generate_expression(&e.expression)?;
34502        self.write(")");
34503        Ok(())
34504    }
34505
34506    /// Generate the body of a USING SAMPLE or TABLESAMPLE clause:
34507    /// METHOD (size UNIT) [REPEATABLE (seed)]
34508    fn generate_sample_body(&mut self, sample: &Sample) -> Result<()> {
34509        // Handle BUCKET sampling: TABLESAMPLE (BUCKET n OUT OF m [ON col])
34510        if matches!(sample.method, SampleMethod::Bucket) {
34511            self.write(" (");
34512            self.write_keyword("BUCKET");
34513            self.write_space();
34514            if let Some(ref num) = sample.bucket_numerator {
34515                self.generate_expression(num)?;
34516            }
34517            self.write_space();
34518            self.write_keyword("OUT OF");
34519            self.write_space();
34520            if let Some(ref denom) = sample.bucket_denominator {
34521                self.generate_expression(denom)?;
34522            }
34523            if let Some(ref field) = sample.bucket_field {
34524                self.write_space();
34525                self.write_keyword("ON");
34526                self.write_space();
34527                self.generate_expression(field)?;
34528            }
34529            self.write(")");
34530            return Ok(());
34531        }
34532
34533        // Output method name if explicitly specified, or for dialects that always require it
34534        let is_snowflake = matches!(
34535            self.config.dialect,
34536            Some(crate::dialects::DialectType::Snowflake)
34537        );
34538        let is_postgres = matches!(
34539            self.config.dialect,
34540            Some(crate::dialects::DialectType::PostgreSQL)
34541                | Some(crate::dialects::DialectType::Redshift)
34542        );
34543        // Databricks and Spark don't output method names
34544        let is_databricks = matches!(
34545            self.config.dialect,
34546            Some(crate::dialects::DialectType::Databricks)
34547        );
34548        let is_spark = matches!(
34549            self.config.dialect,
34550            Some(crate::dialects::DialectType::Spark)
34551        );
34552        let suppress_method = is_databricks || is_spark || sample.suppress_method_output;
34553        // PostgreSQL always outputs BERNOULLI for BERNOULLI samples
34554        let force_method = is_postgres && matches!(sample.method, SampleMethod::Bernoulli);
34555        if !suppress_method && (sample.explicit_method || is_snowflake || force_method) {
34556            self.write_space();
34557            if !sample.explicit_method && (is_snowflake || force_method) {
34558                // Snowflake/PostgreSQL defaults to BERNOULLI when no method is specified
34559                self.write_keyword("BERNOULLI");
34560            } else {
34561                match sample.method {
34562                    SampleMethod::Bernoulli => self.write_keyword("BERNOULLI"),
34563                    SampleMethod::System => self.write_keyword("SYSTEM"),
34564                    SampleMethod::Block => self.write_keyword("BLOCK"),
34565                    SampleMethod::Row => self.write_keyword("ROW"),
34566                    SampleMethod::Reservoir => self.write_keyword("RESERVOIR"),
34567                    SampleMethod::Percent => self.write_keyword("SYSTEM"),
34568                    SampleMethod::Bucket => {} // handled above
34569                }
34570            }
34571        }
34572
34573        // Output size, with or without parentheses depending on dialect
34574        let emit_size_no_parens = !self.config.tablesample_requires_parens;
34575        if emit_size_no_parens {
34576            self.write_space();
34577            match &sample.size {
34578                Expression::Tuple(tuple) => {
34579                    for (i, expr) in tuple.expressions.iter().enumerate() {
34580                        if i > 0 {
34581                            self.write(", ");
34582                        }
34583                        self.generate_expression(expr)?;
34584                    }
34585                }
34586                expr => self.generate_expression(expr)?,
34587            }
34588        } else {
34589            self.write(" (");
34590            self.generate_expression(&sample.size)?;
34591        }
34592
34593        // Determine unit
34594        let is_rows_method = matches!(
34595            sample.method,
34596            SampleMethod::Reservoir | SampleMethod::Row | SampleMethod::Bucket
34597        );
34598        let is_percent = matches!(
34599            sample.method,
34600            SampleMethod::Percent
34601                | SampleMethod::System
34602                | SampleMethod::Bernoulli
34603                | SampleMethod::Block
34604        );
34605
34606        // For Snowflake, PostgreSQL, and Presto/Trino, only output ROWS/PERCENT when the user explicitly wrote it (unit_after_size).
34607        // These dialects use bare numbers for percentage by default in TABLESAMPLE METHOD(size) syntax.
34608        // For Databricks and Spark, always output PERCENT for percentage samples.
34609        let is_presto = matches!(
34610            self.config.dialect,
34611            Some(crate::dialects::DialectType::Presto)
34612                | Some(crate::dialects::DialectType::Trino)
34613                | Some(crate::dialects::DialectType::Athena)
34614        );
34615        let should_output_unit = if is_databricks || is_spark {
34616            // Always output PERCENT for percentage-based methods, or ROWS for row-based methods
34617            is_percent || is_rows_method || sample.unit_after_size
34618        } else if is_snowflake || is_postgres || is_presto {
34619            sample.unit_after_size
34620        } else {
34621            sample.unit_after_size || (sample.explicit_method && (is_rows_method || is_percent))
34622        };
34623
34624        if should_output_unit {
34625            self.write_space();
34626            if sample.is_percent {
34627                self.write_keyword("PERCENT");
34628            } else if is_rows_method && !sample.unit_after_size {
34629                self.write_keyword("ROWS");
34630            } else if sample.unit_after_size {
34631                match sample.method {
34632                    SampleMethod::Percent
34633                    | SampleMethod::System
34634                    | SampleMethod::Bernoulli
34635                    | SampleMethod::Block => {
34636                        self.write_keyword("PERCENT");
34637                    }
34638                    SampleMethod::Row | SampleMethod::Reservoir => {
34639                        self.write_keyword("ROWS");
34640                    }
34641                    _ => self.write_keyword("ROWS"),
34642                }
34643            } else {
34644                self.write_keyword("PERCENT");
34645            }
34646        }
34647
34648        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34649            if let Some(ref offset) = sample.offset {
34650                self.write_space();
34651                self.write_keyword("OFFSET");
34652                self.write_space();
34653                self.generate_expression(offset)?;
34654            }
34655        }
34656        if !emit_size_no_parens {
34657            self.write(")");
34658        }
34659
34660        Ok(())
34661    }
34662
34663    fn generate_sample_property(&mut self, e: &SampleProperty) -> Result<()> {
34664        // SAMPLE this (ClickHouse uses SAMPLE BY)
34665        if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
34666            self.write_keyword("SAMPLE BY");
34667        } else {
34668            self.write_keyword("SAMPLE");
34669        }
34670        self.write_space();
34671        self.generate_expression(&e.this)?;
34672        Ok(())
34673    }
34674
34675    fn generate_schema(&mut self, e: &Schema) -> Result<()> {
34676        // this (expressions...)
34677        if let Some(this) = &e.this {
34678            self.generate_expression(this)?;
34679        }
34680        if !e.expressions.is_empty() {
34681            // Add space before column list if there's a preceding expression
34682            if e.this.is_some() {
34683                self.write_space();
34684            }
34685            self.write("(");
34686            for (i, expr) in e.expressions.iter().enumerate() {
34687                if i > 0 {
34688                    self.write(", ");
34689                }
34690                self.generate_expression(expr)?;
34691            }
34692            self.write(")");
34693        }
34694        Ok(())
34695    }
34696
34697    fn generate_schema_comment_property(&mut self, e: &SchemaCommentProperty) -> Result<()> {
34698        // COMMENT this
34699        self.write_keyword("COMMENT");
34700        self.write_space();
34701        self.generate_expression(&e.this)?;
34702        Ok(())
34703    }
34704
34705    fn generate_scope_resolution(&mut self, e: &ScopeResolution) -> Result<()> {
34706        // [this::]expression
34707        if let Some(this) = &e.this {
34708            self.generate_expression(this)?;
34709            self.write("::");
34710        }
34711        self.generate_expression(&e.expression)?;
34712        Ok(())
34713    }
34714
34715    fn generate_search(&mut self, e: &Search) -> Result<()> {
34716        // SEARCH(this, expression, [json_scope], [analyzer], [analyzer_options], [search_mode])
34717        self.write_keyword("SEARCH");
34718        self.write("(");
34719        self.generate_expression(&e.this)?;
34720        self.write(", ");
34721        self.generate_expression(&e.expression)?;
34722        if let Some(json_scope) = &e.json_scope {
34723            self.write(", ");
34724            self.generate_expression(json_scope)?;
34725        }
34726        if let Some(analyzer) = &e.analyzer {
34727            self.write(", ");
34728            self.generate_expression(analyzer)?;
34729        }
34730        if let Some(analyzer_options) = &e.analyzer_options {
34731            self.write(", ");
34732            self.generate_expression(analyzer_options)?;
34733        }
34734        if let Some(search_mode) = &e.search_mode {
34735            self.write(", ");
34736            self.generate_expression(search_mode)?;
34737        }
34738        self.write(")");
34739        Ok(())
34740    }
34741
34742    fn generate_search_ip(&mut self, e: &SearchIp) -> Result<()> {
34743        // SEARCH_IP(this, expression)
34744        self.write_keyword("SEARCH_IP");
34745        self.write("(");
34746        self.generate_expression(&e.this)?;
34747        self.write(", ");
34748        self.generate_expression(&e.expression)?;
34749        self.write(")");
34750        Ok(())
34751    }
34752
34753    fn generate_security_property(&mut self, e: &SecurityProperty) -> Result<()> {
34754        // SECURITY this
34755        self.write_keyword("SECURITY");
34756        self.write_space();
34757        self.generate_expression(&e.this)?;
34758        Ok(())
34759    }
34760
34761    fn generate_semantic_view(&mut self, e: &SemanticView) -> Result<()> {
34762        // SEMANTIC_VIEW(this [METRICS ...] [DIMENSIONS ...] [FACTS ...] [WHERE ...])
34763        self.write("SEMANTIC_VIEW(");
34764
34765        if self.config.pretty {
34766            // Pretty print: each clause on its own line
34767            self.write_newline();
34768            self.indent_level += 1;
34769            self.write_indent();
34770            self.generate_expression(&e.this)?;
34771
34772            if let Some(metrics) = &e.metrics {
34773                self.write_newline();
34774                self.write_indent();
34775                self.write_keyword("METRICS");
34776                self.write_space();
34777                self.generate_semantic_view_tuple(metrics)?;
34778            }
34779            if let Some(dimensions) = &e.dimensions {
34780                self.write_newline();
34781                self.write_indent();
34782                self.write_keyword("DIMENSIONS");
34783                self.write_space();
34784                self.generate_semantic_view_tuple(dimensions)?;
34785            }
34786            if let Some(facts) = &e.facts {
34787                self.write_newline();
34788                self.write_indent();
34789                self.write_keyword("FACTS");
34790                self.write_space();
34791                self.generate_semantic_view_tuple(facts)?;
34792            }
34793            if let Some(where_) = &e.where_ {
34794                self.write_newline();
34795                self.write_indent();
34796                self.write_keyword("WHERE");
34797                self.write_space();
34798                self.generate_expression(where_)?;
34799            }
34800            self.write_newline();
34801            self.indent_level -= 1;
34802            self.write_indent();
34803        } else {
34804            // Compact: all on one line
34805            self.generate_expression(&e.this)?;
34806            if let Some(metrics) = &e.metrics {
34807                self.write_space();
34808                self.write_keyword("METRICS");
34809                self.write_space();
34810                self.generate_semantic_view_tuple(metrics)?;
34811            }
34812            if let Some(dimensions) = &e.dimensions {
34813                self.write_space();
34814                self.write_keyword("DIMENSIONS");
34815                self.write_space();
34816                self.generate_semantic_view_tuple(dimensions)?;
34817            }
34818            if let Some(facts) = &e.facts {
34819                self.write_space();
34820                self.write_keyword("FACTS");
34821                self.write_space();
34822                self.generate_semantic_view_tuple(facts)?;
34823            }
34824            if let Some(where_) = &e.where_ {
34825                self.write_space();
34826                self.write_keyword("WHERE");
34827                self.write_space();
34828                self.generate_expression(where_)?;
34829            }
34830        }
34831        self.write(")");
34832        Ok(())
34833    }
34834
34835    /// Helper for SEMANTIC_VIEW tuple contents (without parentheses)
34836    fn generate_semantic_view_tuple(&mut self, expr: &Expression) -> Result<()> {
34837        if let Expression::Tuple(t) = expr {
34838            for (i, e) in t.expressions.iter().enumerate() {
34839                if i > 0 {
34840                    self.write(", ");
34841                }
34842                self.generate_expression(e)?;
34843            }
34844        } else {
34845            self.generate_expression(expr)?;
34846        }
34847        Ok(())
34848    }
34849
34850    fn generate_sequence_properties(&mut self, e: &SequenceProperties) -> Result<()> {
34851        // [START WITH start] [INCREMENT BY increment] [MINVALUE minvalue] [MAXVALUE maxvalue] [CACHE cache] [OWNED BY owned]
34852        if let Some(start) = &e.start {
34853            self.write_keyword("START WITH");
34854            self.write_space();
34855            self.generate_expression(start)?;
34856        }
34857        if let Some(increment) = &e.increment {
34858            self.write_space();
34859            self.write_keyword("INCREMENT BY");
34860            self.write_space();
34861            self.generate_expression(increment)?;
34862        }
34863        if let Some(minvalue) = &e.minvalue {
34864            self.write_space();
34865            self.write_keyword("MINVALUE");
34866            self.write_space();
34867            self.generate_expression(minvalue)?;
34868        }
34869        if let Some(maxvalue) = &e.maxvalue {
34870            self.write_space();
34871            self.write_keyword("MAXVALUE");
34872            self.write_space();
34873            self.generate_expression(maxvalue)?;
34874        }
34875        if let Some(cache) = &e.cache {
34876            self.write_space();
34877            self.write_keyword("CACHE");
34878            self.write_space();
34879            self.generate_expression(cache)?;
34880        }
34881        if let Some(owned) = &e.owned {
34882            self.write_space();
34883            self.write_keyword("OWNED BY");
34884            self.write_space();
34885            self.generate_expression(owned)?;
34886        }
34887        for opt in &e.options {
34888            self.write_space();
34889            self.generate_expression(opt)?;
34890        }
34891        Ok(())
34892    }
34893
34894    fn generate_serde_properties(&mut self, e: &SerdeProperties) -> Result<()> {
34895        // [WITH] SERDEPROPERTIES (expressions)
34896        if e.with_.is_some() {
34897            self.write_keyword("WITH");
34898            self.write_space();
34899        }
34900        self.write_keyword("SERDEPROPERTIES");
34901        self.write(" (");
34902        for (i, expr) in e.expressions.iter().enumerate() {
34903            if i > 0 {
34904                self.write(", ");
34905            }
34906            // Generate key=value without spaces around =
34907            match expr {
34908                Expression::Eq(eq) => {
34909                    self.generate_expression(&eq.left)?;
34910                    self.write("=");
34911                    self.generate_expression(&eq.right)?;
34912                }
34913                _ => self.generate_expression(expr)?,
34914            }
34915        }
34916        self.write(")");
34917        Ok(())
34918    }
34919
34920    fn generate_session_parameter(&mut self, e: &SessionParameter) -> Result<()> {
34921        // @@[kind.]this
34922        self.write("@@");
34923        if let Some(kind) = &e.kind {
34924            self.write(kind);
34925            self.write(".");
34926        }
34927        self.generate_expression(&e.this)?;
34928        Ok(())
34929    }
34930
34931    fn generate_set(&mut self, e: &Set) -> Result<()> {
34932        // SET/UNSET [TAG] expressions
34933        if e.unset.is_some() {
34934            self.write_keyword("UNSET");
34935        } else {
34936            self.write_keyword("SET");
34937        }
34938        if e.tag.is_some() {
34939            self.write_space();
34940            self.write_keyword("TAG");
34941        }
34942        if !e.expressions.is_empty() {
34943            self.write_space();
34944            for (i, expr) in e.expressions.iter().enumerate() {
34945                if i > 0 {
34946                    self.write(", ");
34947                }
34948                self.generate_expression(expr)?;
34949            }
34950        }
34951        Ok(())
34952    }
34953
34954    fn generate_set_config_property(&mut self, e: &SetConfigProperty) -> Result<()> {
34955        // SET this or SETCONFIG this
34956        self.write_keyword("SET");
34957        self.write_space();
34958        self.generate_expression(&e.this)?;
34959        Ok(())
34960    }
34961
34962    fn generate_set_item(&mut self, e: &SetItem) -> Result<()> {
34963        // [kind] name = value
34964        if let Some(kind) = &e.kind {
34965            self.write_keyword(kind);
34966            self.write_space();
34967        }
34968        self.generate_expression(&e.name)?;
34969        self.write(" = ");
34970        self.generate_expression(&e.value)?;
34971        Ok(())
34972    }
34973
34974    fn generate_set_operation(&mut self, e: &SetOperation) -> Result<()> {
34975        // [WITH ...] this UNION|INTERSECT|EXCEPT [ALL|DISTINCT] [BY NAME] expression
34976        if let Some(with_) = &e.with_ {
34977            self.generate_expression(with_)?;
34978            self.write_space();
34979        }
34980        self.generate_expression(&e.this)?;
34981        self.write_space();
34982        // kind should be UNION, INTERSECT, EXCEPT, etc.
34983        if let Some(kind) = &e.kind {
34984            self.write_keyword(kind);
34985        }
34986        if e.distinct {
34987            self.write_space();
34988            self.write_keyword("DISTINCT");
34989        } else {
34990            self.write_space();
34991            self.write_keyword("ALL");
34992        }
34993        if e.by_name.is_some() {
34994            self.write_space();
34995            self.write_keyword("BY NAME");
34996        }
34997        self.write_space();
34998        self.generate_expression(&e.expression)?;
34999        Ok(())
35000    }
35001
35002    fn generate_set_property(&mut self, e: &SetProperty) -> Result<()> {
35003        // SET or MULTISET
35004        if e.multi.is_some() {
35005            self.write_keyword("MULTISET");
35006        } else {
35007            self.write_keyword("SET");
35008        }
35009        Ok(())
35010    }
35011
35012    fn generate_settings_property(&mut self, e: &SettingsProperty) -> Result<()> {
35013        // SETTINGS expressions
35014        self.write_keyword("SETTINGS");
35015        if self.config.pretty && e.expressions.len() > 1 {
35016            // Pretty print: each setting on its own line, indented
35017            self.indent_level += 1;
35018            for (i, expr) in e.expressions.iter().enumerate() {
35019                if i > 0 {
35020                    self.write(",");
35021                }
35022                self.write_newline();
35023                self.write_indent();
35024                self.generate_expression(expr)?;
35025            }
35026            self.indent_level -= 1;
35027        } else {
35028            self.write_space();
35029            for (i, expr) in e.expressions.iter().enumerate() {
35030                if i > 0 {
35031                    self.write(", ");
35032                }
35033                self.generate_expression(expr)?;
35034            }
35035        }
35036        Ok(())
35037    }
35038
35039    fn generate_sharing_property(&mut self, e: &SharingProperty) -> Result<()> {
35040        // SHARING = this
35041        self.write_keyword("SHARING");
35042        if let Some(this) = &e.this {
35043            self.write(" = ");
35044            self.generate_expression(this)?;
35045        }
35046        Ok(())
35047    }
35048
35049    fn generate_slice(&mut self, e: &Slice) -> Result<()> {
35050        // Python array slicing: begin:end:step
35051        if let Some(begin) = &e.this {
35052            self.generate_expression(begin)?;
35053        }
35054        self.write(":");
35055        if let Some(end) = &e.expression {
35056            self.generate_expression(end)?;
35057        }
35058        if let Some(step) = &e.step {
35059            self.write(":");
35060            self.generate_expression(step)?;
35061        }
35062        Ok(())
35063    }
35064
35065    fn generate_sort_array(&mut self, e: &SortArray) -> Result<()> {
35066        // SORT_ARRAY(this, asc)
35067        self.write_keyword("SORT_ARRAY");
35068        self.write("(");
35069        self.generate_expression(&e.this)?;
35070        if let Some(asc) = &e.asc {
35071            self.write(", ");
35072            self.generate_expression(asc)?;
35073        }
35074        self.write(")");
35075        Ok(())
35076    }
35077
35078    fn generate_sort_by(&mut self, e: &SortBy) -> Result<()> {
35079        // SORT BY expressions
35080        self.write_keyword("SORT BY");
35081        self.write_space();
35082        for (i, expr) in e.expressions.iter().enumerate() {
35083            if i > 0 {
35084                self.write(", ");
35085            }
35086            self.generate_ordered(expr)?;
35087        }
35088        Ok(())
35089    }
35090
35091    fn generate_sort_key_property(&mut self, e: &SortKeyProperty) -> Result<()> {
35092        // [COMPOUND] SORTKEY(col1, col2, ...) - no space before paren
35093        if e.compound.is_some() {
35094            self.write_keyword("COMPOUND");
35095            self.write_space();
35096        }
35097        self.write_keyword("SORTKEY");
35098        self.write("(");
35099        // If this is a Tuple, unwrap its contents to avoid double parentheses
35100        if let Expression::Tuple(t) = e.this.as_ref() {
35101            for (i, expr) in t.expressions.iter().enumerate() {
35102                if i > 0 {
35103                    self.write(", ");
35104                }
35105                self.generate_expression(expr)?;
35106            }
35107        } else {
35108            self.generate_expression(&e.this)?;
35109        }
35110        self.write(")");
35111        Ok(())
35112    }
35113
35114    fn generate_split_part(&mut self, e: &SplitPart) -> Result<()> {
35115        // SPLIT_PART(this, delimiter, part_index)
35116        self.write_keyword("SPLIT_PART");
35117        self.write("(");
35118        self.generate_expression(&e.this)?;
35119        if let Some(delimiter) = &e.delimiter {
35120            self.write(", ");
35121            self.generate_expression(delimiter)?;
35122        }
35123        if let Some(part_index) = &e.part_index {
35124            self.write(", ");
35125            self.generate_expression(part_index)?;
35126        }
35127        self.write(")");
35128        Ok(())
35129    }
35130
35131    fn generate_sql_read_write_property(&mut self, e: &SqlReadWriteProperty) -> Result<()> {
35132        // READS SQL DATA or MODIFIES SQL DATA, etc.
35133        self.generate_expression(&e.this)?;
35134        Ok(())
35135    }
35136
35137    fn generate_sql_security_property(&mut self, e: &SqlSecurityProperty) -> Result<()> {
35138        // SQL SECURITY DEFINER or SQL SECURITY INVOKER
35139        self.write_keyword("SQL SECURITY");
35140        self.write_space();
35141        self.generate_expression(&e.this)?;
35142        Ok(())
35143    }
35144
35145    fn generate_st_distance(&mut self, e: &StDistance) -> Result<()> {
35146        // ST_DISTANCE(this, expression, [use_spheroid])
35147        self.write_keyword("ST_DISTANCE");
35148        self.write("(");
35149        self.generate_expression(&e.this)?;
35150        self.write(", ");
35151        self.generate_expression(&e.expression)?;
35152        if let Some(use_spheroid) = &e.use_spheroid {
35153            self.write(", ");
35154            self.generate_expression(use_spheroid)?;
35155        }
35156        self.write(")");
35157        Ok(())
35158    }
35159
35160    fn generate_st_point(&mut self, e: &StPoint) -> Result<()> {
35161        // ST_POINT(this, expression)
35162        self.write_keyword("ST_POINT");
35163        self.write("(");
35164        self.generate_expression(&e.this)?;
35165        self.write(", ");
35166        self.generate_expression(&e.expression)?;
35167        self.write(")");
35168        Ok(())
35169    }
35170
35171    fn generate_stability_property(&mut self, e: &StabilityProperty) -> Result<()> {
35172        // IMMUTABLE, STABLE, VOLATILE
35173        self.generate_expression(&e.this)?;
35174        Ok(())
35175    }
35176
35177    fn generate_standard_hash(&mut self, e: &StandardHash) -> Result<()> {
35178        // STANDARD_HASH(this, [expression])
35179        self.write_keyword("STANDARD_HASH");
35180        self.write("(");
35181        self.generate_expression(&e.this)?;
35182        if let Some(expression) = &e.expression {
35183            self.write(", ");
35184            self.generate_expression(expression)?;
35185        }
35186        self.write(")");
35187        Ok(())
35188    }
35189
35190    fn generate_storage_handler_property(&mut self, e: &StorageHandlerProperty) -> Result<()> {
35191        // STORED BY this
35192        self.write_keyword("STORED BY");
35193        self.write_space();
35194        self.generate_expression(&e.this)?;
35195        Ok(())
35196    }
35197
35198    fn generate_str_position(&mut self, e: &StrPosition) -> Result<()> {
35199        // STRPOS(this, substr) or STRPOS(this, substr, position)
35200        // Different dialects have different function names
35201        use crate::dialects::DialectType;
35202        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
35203            // Snowflake: CHARINDEX(substr, str[, position])
35204            self.write_keyword("CHARINDEX");
35205            self.write("(");
35206            if let Some(substr) = &e.substr {
35207                self.generate_expression(substr)?;
35208                self.write(", ");
35209            }
35210            self.generate_expression(&e.this)?;
35211            if let Some(position) = &e.position {
35212                self.write(", ");
35213                self.generate_expression(position)?;
35214            }
35215            self.write(")");
35216        } else if matches!(self.config.dialect, Some(DialectType::ClickHouse)) {
35217            self.write_keyword("POSITION");
35218            self.write("(");
35219            self.generate_expression(&e.this)?;
35220            if let Some(substr) = &e.substr {
35221                self.write(", ");
35222                self.generate_expression(substr)?;
35223            }
35224            if let Some(position) = &e.position {
35225                self.write(", ");
35226                self.generate_expression(position)?;
35227            }
35228            if let Some(occurrence) = &e.occurrence {
35229                self.write(", ");
35230                self.generate_expression(occurrence)?;
35231            }
35232            self.write(")");
35233        } else if matches!(
35234            self.config.dialect,
35235            Some(DialectType::SQLite)
35236                | Some(DialectType::Oracle)
35237                | Some(DialectType::BigQuery)
35238                | Some(DialectType::Teradata)
35239        ) {
35240            self.write_keyword("INSTR");
35241            self.write("(");
35242            self.generate_expression(&e.this)?;
35243            if let Some(substr) = &e.substr {
35244                self.write(", ");
35245                self.generate_expression(substr)?;
35246            }
35247            if let Some(position) = &e.position {
35248                self.write(", ");
35249                self.generate_expression(position)?;
35250            } else if e.occurrence.is_some() {
35251                // INSTR requires a position arg before occurrence: INSTR(str, substr, start, nth)
35252                // Default start position is 1
35253                self.write(", 1");
35254            }
35255            if let Some(occurrence) = &e.occurrence {
35256                self.write(", ");
35257                self.generate_expression(occurrence)?;
35258            }
35259            self.write(")");
35260        } else if matches!(
35261            self.config.dialect,
35262            Some(DialectType::MySQL)
35263                | Some(DialectType::SingleStore)
35264                | Some(DialectType::Doris)
35265                | Some(DialectType::StarRocks)
35266                | Some(DialectType::Hive)
35267                | Some(DialectType::Spark)
35268                | Some(DialectType::Databricks)
35269        ) {
35270            // LOCATE(substr, str[, position]) - substr first
35271            self.write_keyword("LOCATE");
35272            self.write("(");
35273            if let Some(substr) = &e.substr {
35274                self.generate_expression(substr)?;
35275                self.write(", ");
35276            }
35277            self.generate_expression(&e.this)?;
35278            if let Some(position) = &e.position {
35279                self.write(", ");
35280                self.generate_expression(position)?;
35281            }
35282            self.write(")");
35283        } else if matches!(
35284            self.config.dialect,
35285            Some(DialectType::TSQL) | Some(DialectType::Fabric)
35286        ) {
35287            // CHARINDEX(substr, str[, position])
35288            self.write_keyword("CHARINDEX");
35289            self.write("(");
35290            if let Some(substr) = &e.substr {
35291                self.generate_expression(substr)?;
35292                self.write(", ");
35293            }
35294            self.generate_expression(&e.this)?;
35295            if let Some(position) = &e.position {
35296                self.write(", ");
35297                self.generate_expression(position)?;
35298            }
35299            self.write(")");
35300        } else if matches!(
35301            self.config.dialect,
35302            Some(DialectType::PostgreSQL)
35303                | Some(DialectType::Materialize)
35304                | Some(DialectType::RisingWave)
35305                | Some(DialectType::Redshift)
35306        ) {
35307            // POSITION(substr IN str) syntax
35308            self.write_keyword("POSITION");
35309            self.write("(");
35310            if let Some(substr) = &e.substr {
35311                self.generate_expression(substr)?;
35312                self.write(" IN ");
35313            }
35314            self.generate_expression(&e.this)?;
35315            self.write(")");
35316        } else {
35317            self.write_keyword("STRPOS");
35318            self.write("(");
35319            self.generate_expression(&e.this)?;
35320            if let Some(substr) = &e.substr {
35321                self.write(", ");
35322                self.generate_expression(substr)?;
35323            }
35324            if let Some(position) = &e.position {
35325                self.write(", ");
35326                self.generate_expression(position)?;
35327            }
35328            if let Some(occurrence) = &e.occurrence {
35329                self.write(", ");
35330                self.generate_expression(occurrence)?;
35331            }
35332            self.write(")");
35333        }
35334        Ok(())
35335    }
35336
35337    fn generate_str_to_date(&mut self, e: &StrToDate) -> Result<()> {
35338        match self.config.dialect {
35339            Some(DialectType::Spark) | Some(DialectType::Databricks) | Some(DialectType::Hive) => {
35340                // TO_DATE(this, java_format)
35341                self.write_keyword("TO_DATE");
35342                self.write("(");
35343                self.generate_expression(&e.this)?;
35344                if let Some(format) = &e.format {
35345                    self.write(", '");
35346                    self.write(&Self::strftime_to_java_format(format));
35347                    self.write("'");
35348                }
35349                self.write(")");
35350            }
35351            Some(DialectType::DuckDB) => {
35352                // CAST(STRPTIME(this, format) AS DATE)
35353                self.write_keyword("CAST");
35354                self.write("(");
35355                self.write_keyword("STRPTIME");
35356                self.write("(");
35357                self.generate_expression(&e.this)?;
35358                if let Some(format) = &e.format {
35359                    self.write(", '");
35360                    self.write(format);
35361                    self.write("'");
35362                }
35363                self.write(")");
35364                self.write_keyword(" AS ");
35365                self.write_keyword("DATE");
35366                self.write(")");
35367            }
35368            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => {
35369                // TO_DATE(this, pg_format)
35370                self.write_keyword("TO_DATE");
35371                self.write("(");
35372                self.generate_expression(&e.this)?;
35373                if let Some(format) = &e.format {
35374                    self.write(", '");
35375                    self.write(&Self::strftime_to_postgres_format(format));
35376                    self.write("'");
35377                }
35378                self.write(")");
35379            }
35380            Some(DialectType::BigQuery) => {
35381                // PARSE_DATE(format, this) - note: format comes first for BigQuery
35382                self.write_keyword("PARSE_DATE");
35383                self.write("(");
35384                if let Some(format) = &e.format {
35385                    self.write("'");
35386                    self.write(format);
35387                    self.write("'");
35388                    self.write(", ");
35389                }
35390                self.generate_expression(&e.this)?;
35391                self.write(")");
35392            }
35393            Some(DialectType::Teradata) => {
35394                // CAST(this AS DATE FORMAT 'teradata_fmt')
35395                self.write_keyword("CAST");
35396                self.write("(");
35397                self.generate_expression(&e.this)?;
35398                self.write_keyword(" AS ");
35399                self.write_keyword("DATE");
35400                if let Some(format) = &e.format {
35401                    self.write_keyword(" FORMAT ");
35402                    self.write("'");
35403                    self.write(&Self::strftime_to_teradata_format(format));
35404                    self.write("'");
35405                }
35406                self.write(")");
35407            }
35408            _ => {
35409                // STR_TO_DATE(this, format) - MySQL default
35410                self.write_keyword("STR_TO_DATE");
35411                self.write("(");
35412                self.generate_expression(&e.this)?;
35413                if let Some(format) = &e.format {
35414                    self.write(", '");
35415                    self.write(format);
35416                    self.write("'");
35417                }
35418                self.write(")");
35419            }
35420        }
35421        Ok(())
35422    }
35423
35424    /// Convert strftime format to Teradata date format (YYYY, DD, MM, etc.)
35425    fn strftime_to_teradata_format(fmt: &str) -> String {
35426        let mut result = String::with_capacity(fmt.len() * 2);
35427        let bytes = fmt.as_bytes();
35428        let len = bytes.len();
35429        let mut i = 0;
35430        while i < len {
35431            if bytes[i] == b'%' && i + 1 < len {
35432                let replacement = match bytes[i + 1] {
35433                    b'Y' => "YYYY",
35434                    b'y' => "YY",
35435                    b'm' => "MM",
35436                    b'B' => "MMMM",
35437                    b'b' => "MMM",
35438                    b'd' => "DD",
35439                    b'j' => "DDD",
35440                    b'H' => "HH",
35441                    b'M' => "MI",
35442                    b'S' => "SS",
35443                    b'f' => "SSSSSS",
35444                    b'A' => "EEEE",
35445                    b'a' => "EEE",
35446                    _ => {
35447                        result.push('%');
35448                        i += 1;
35449                        continue;
35450                    }
35451                };
35452                result.push_str(replacement);
35453                i += 2;
35454            } else {
35455                result.push(bytes[i] as char);
35456                i += 1;
35457            }
35458        }
35459        result
35460    }
35461
35462    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
35463    /// Public static version for use by other modules
35464    pub fn strftime_to_java_format_static(fmt: &str) -> String {
35465        Self::strftime_to_java_format(fmt)
35466    }
35467
35468    /// Convert strftime format (%Y, %m, %d, etc.) to Java date format (yyyy, MM, dd, etc.)
35469    fn strftime_to_java_format(fmt: &str) -> String {
35470        let mut result = String::with_capacity(fmt.len() * 2);
35471        let bytes = fmt.as_bytes();
35472        let len = bytes.len();
35473        let mut i = 0;
35474        while i < len {
35475            if bytes[i] == b'%' && i + 1 < len {
35476                // Check for non-padded variants (%-X)
35477                if bytes[i + 1] == b'-' && i + 2 < len {
35478                    let replacement = match bytes[i + 2] {
35479                        b'd' => "d",
35480                        b'm' => "M",
35481                        b'H' => "H",
35482                        b'M' => "m",
35483                        b'S' => "s",
35484                        _ => {
35485                            result.push('%');
35486                            i += 1;
35487                            continue;
35488                        }
35489                    };
35490                    result.push_str(replacement);
35491                    i += 3;
35492                } else {
35493                    let replacement = match bytes[i + 1] {
35494                        b'Y' => "yyyy",
35495                        b'y' => "yy",
35496                        b'm' => "MM",
35497                        b'B' => "MMMM",
35498                        b'b' => "MMM",
35499                        b'd' => "dd",
35500                        b'j' => "DDD",
35501                        b'H' => "HH",
35502                        b'M' => "mm",
35503                        b'S' => "ss",
35504                        b'f' => "SSSSSS",
35505                        b'A' => "EEEE",
35506                        b'a' => "EEE",
35507                        _ => {
35508                            result.push('%');
35509                            i += 1;
35510                            continue;
35511                        }
35512                    };
35513                    result.push_str(replacement);
35514                    i += 2;
35515                }
35516            } else {
35517                result.push(bytes[i] as char);
35518                i += 1;
35519            }
35520        }
35521        result
35522    }
35523
35524    /// Convert strftime format (%Y, %m, %d, etc.) to .NET date format for TSQL FORMAT()
35525    /// Similar to Java but uses ffffff for microseconds instead of SSSSSS
35526    fn strftime_to_tsql_format(fmt: &str) -> String {
35527        let mut result = String::with_capacity(fmt.len() * 2);
35528        let bytes = fmt.as_bytes();
35529        let len = bytes.len();
35530        let mut i = 0;
35531        while i < len {
35532            if bytes[i] == b'%' && i + 1 < len {
35533                // Check for non-padded variants (%-X)
35534                if bytes[i + 1] == b'-' && i + 2 < len {
35535                    let replacement = match bytes[i + 2] {
35536                        b'd' => "d",
35537                        b'm' => "M",
35538                        b'H' => "H",
35539                        b'M' => "m",
35540                        b'S' => "s",
35541                        _ => {
35542                            result.push('%');
35543                            i += 1;
35544                            continue;
35545                        }
35546                    };
35547                    result.push_str(replacement);
35548                    i += 3;
35549                } else {
35550                    let replacement = match bytes[i + 1] {
35551                        b'Y' => "yyyy",
35552                        b'y' => "yy",
35553                        b'm' => "MM",
35554                        b'B' => "MMMM",
35555                        b'b' => "MMM",
35556                        b'd' => "dd",
35557                        b'j' => "DDD",
35558                        b'H' => "HH",
35559                        b'M' => "mm",
35560                        b'S' => "ss",
35561                        b'f' => "ffffff",
35562                        b'A' => "dddd",
35563                        b'a' => "ddd",
35564                        _ => {
35565                            result.push('%');
35566                            i += 1;
35567                            continue;
35568                        }
35569                    };
35570                    result.push_str(replacement);
35571                    i += 2;
35572                }
35573            } else {
35574                result.push(bytes[i] as char);
35575                i += 1;
35576            }
35577        }
35578        result
35579    }
35580
35581    /// Decompose a JSON path string like "$.y[0].z" into individual parts: ["y", "0", "z"]
35582    /// This is used for PostgreSQL/Redshift JSON_EXTRACT_PATH / JSON_EXTRACT_PATH_TEXT
35583    fn decompose_json_path(path: &str) -> Vec<String> {
35584        let mut parts = Vec::new();
35585        // Strip leading $ and optional .
35586        let path = if path.starts_with("$.") {
35587            &path[2..]
35588        } else if path.starts_with('$') {
35589            &path[1..]
35590        } else {
35591            path
35592        };
35593        if path.is_empty() {
35594            return parts;
35595        }
35596        let mut current = String::new();
35597        let chars: Vec<char> = path.chars().collect();
35598        let mut i = 0;
35599        while i < chars.len() {
35600            match chars[i] {
35601                '.' => {
35602                    if !current.is_empty() {
35603                        parts.push(current.clone());
35604                        current.clear();
35605                    }
35606                    i += 1;
35607                }
35608                '[' => {
35609                    if !current.is_empty() {
35610                        parts.push(current.clone());
35611                        current.clear();
35612                    }
35613                    i += 1;
35614                    // Read the content inside brackets
35615                    let mut bracket_content = String::new();
35616                    while i < chars.len() && chars[i] != ']' {
35617                        // Skip quotes inside brackets
35618                        if chars[i] == '"' || chars[i] == '\'' {
35619                            let quote = chars[i];
35620                            i += 1;
35621                            while i < chars.len() && chars[i] != quote {
35622                                bracket_content.push(chars[i]);
35623                                i += 1;
35624                            }
35625                            if i < chars.len() {
35626                                i += 1;
35627                            } // skip closing quote
35628                        } else {
35629                            bracket_content.push(chars[i]);
35630                            i += 1;
35631                        }
35632                    }
35633                    if i < chars.len() {
35634                        i += 1;
35635                    } // skip ]
35636                      // Skip wildcard [*] - don't add as a part
35637                    if bracket_content != "*" {
35638                        parts.push(bracket_content);
35639                    }
35640                }
35641                _ => {
35642                    current.push(chars[i]);
35643                    i += 1;
35644                }
35645            }
35646        }
35647        if !current.is_empty() {
35648            parts.push(current);
35649        }
35650        parts
35651    }
35652
35653    /// Convert strftime format to PostgreSQL date format (YYYY, MM, DD, etc.)
35654    fn strftime_to_postgres_format(fmt: &str) -> String {
35655        let mut result = String::with_capacity(fmt.len() * 2);
35656        let bytes = fmt.as_bytes();
35657        let len = bytes.len();
35658        let mut i = 0;
35659        while i < len {
35660            if bytes[i] == b'%' && i + 1 < len {
35661                // Check for non-padded variants (%-X)
35662                if bytes[i + 1] == b'-' && i + 2 < len {
35663                    let replacement = match bytes[i + 2] {
35664                        b'd' => "FMDD",
35665                        b'm' => "FMMM",
35666                        b'H' => "FMHH24",
35667                        b'M' => "FMMI",
35668                        b'S' => "FMSS",
35669                        _ => {
35670                            result.push('%');
35671                            i += 1;
35672                            continue;
35673                        }
35674                    };
35675                    result.push_str(replacement);
35676                    i += 3;
35677                } else {
35678                    let replacement = match bytes[i + 1] {
35679                        b'Y' => "YYYY",
35680                        b'y' => "YY",
35681                        b'm' => "MM",
35682                        b'B' => "Month",
35683                        b'b' => "Mon",
35684                        b'd' => "DD",
35685                        b'j' => "DDD",
35686                        b'H' => "HH24",
35687                        b'M' => "MI",
35688                        b'S' => "SS",
35689                        b'f' => "US",
35690                        b'A' => "Day",
35691                        b'a' => "Dy",
35692                        _ => {
35693                            result.push('%');
35694                            i += 1;
35695                            continue;
35696                        }
35697                    };
35698                    result.push_str(replacement);
35699                    i += 2;
35700                }
35701            } else {
35702                result.push(bytes[i] as char);
35703                i += 1;
35704            }
35705        }
35706        result
35707    }
35708
35709    /// Convert strftime format to Snowflake date format (yyyy, mm, DD, etc.)
35710    fn strftime_to_snowflake_format(fmt: &str) -> String {
35711        let mut result = String::with_capacity(fmt.len() * 2);
35712        let bytes = fmt.as_bytes();
35713        let len = bytes.len();
35714        let mut i = 0;
35715        while i < len {
35716            if bytes[i] == b'%' && i + 1 < len {
35717                // Check for non-padded variants (%-X)
35718                if bytes[i + 1] == b'-' && i + 2 < len {
35719                    let replacement = match bytes[i + 2] {
35720                        b'd' => "dd",
35721                        b'm' => "mm",
35722                        _ => {
35723                            result.push('%');
35724                            i += 1;
35725                            continue;
35726                        }
35727                    };
35728                    result.push_str(replacement);
35729                    i += 3;
35730                } else {
35731                    let replacement = match bytes[i + 1] {
35732                        b'Y' => "yyyy",
35733                        b'y' => "yy",
35734                        b'm' => "mm",
35735                        b'd' => "DD",
35736                        b'H' => "hh24",
35737                        b'M' => "mi",
35738                        b'S' => "ss",
35739                        b'f' => "ff",
35740                        _ => {
35741                            result.push('%');
35742                            i += 1;
35743                            continue;
35744                        }
35745                    };
35746                    result.push_str(replacement);
35747                    i += 2;
35748                }
35749            } else {
35750                result.push(bytes[i] as char);
35751                i += 1;
35752            }
35753        }
35754        result
35755    }
35756
35757    fn generate_str_to_map(&mut self, e: &StrToMap) -> Result<()> {
35758        // STR_TO_MAP(this, pair_delim, key_value_delim)
35759        self.write_keyword("STR_TO_MAP");
35760        self.write("(");
35761        self.generate_expression(&e.this)?;
35762        // Spark/Hive: STR_TO_MAP needs explicit default delimiters
35763        let needs_defaults = matches!(
35764            self.config.dialect,
35765            Some(DialectType::Spark) | Some(DialectType::Hive) | Some(DialectType::Databricks)
35766        );
35767        if let Some(pair_delim) = &e.pair_delim {
35768            self.write(", ");
35769            self.generate_expression(pair_delim)?;
35770        } else if needs_defaults {
35771            self.write(", ','");
35772        }
35773        if let Some(key_value_delim) = &e.key_value_delim {
35774            self.write(", ");
35775            self.generate_expression(key_value_delim)?;
35776        } else if needs_defaults {
35777            self.write(", ':'");
35778        }
35779        self.write(")");
35780        Ok(())
35781    }
35782
35783    fn generate_str_to_time(&mut self, e: &StrToTime) -> Result<()> {
35784        // Detect format style: strftime (starts with %) vs Snowflake/Java
35785        let is_strftime = e.format.contains('%');
35786        // Helper: get strftime format from whatever style is stored
35787        let to_strftime = |f: &str| -> String {
35788            if is_strftime {
35789                f.to_string()
35790            } else {
35791                Self::snowflake_format_to_strftime(f)
35792            }
35793        };
35794        // Helper: get Java format
35795        let to_java = |f: &str| -> String {
35796            if is_strftime {
35797                Self::strftime_to_java_format(f)
35798            } else {
35799                Self::snowflake_format_to_spark(f)
35800            }
35801        };
35802        // Helper: get PG format
35803        let to_pg = |f: &str| -> String {
35804            if is_strftime {
35805                Self::strftime_to_postgres_format(f)
35806            } else {
35807                Self::convert_strptime_to_postgres_format(f)
35808            }
35809        };
35810
35811        match self.config.dialect {
35812            Some(DialectType::Exasol) => {
35813                self.write_keyword("TO_DATE");
35814                self.write("(");
35815                self.generate_expression(&e.this)?;
35816                self.write(", '");
35817                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
35818                self.write("'");
35819                self.write(")");
35820            }
35821            Some(DialectType::BigQuery) => {
35822                // BigQuery: PARSE_TIMESTAMP(format, value) - note swapped args
35823                let fmt = to_strftime(&e.format);
35824                // BigQuery normalizes: %Y-%m-%d -> %F, %H:%M:%S -> %T
35825                let fmt = fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
35826                self.write_keyword("PARSE_TIMESTAMP");
35827                self.write("('");
35828                self.write(&fmt);
35829                self.write("', ");
35830                self.generate_expression(&e.this)?;
35831                self.write(")");
35832            }
35833            Some(DialectType::Hive) => {
35834                // Hive: CAST(x AS TIMESTAMP) for simple date formats
35835                // Check both the raw format and the converted format (in case it's already Java)
35836                let java_fmt = to_java(&e.format);
35837                if java_fmt == "yyyy-MM-dd HH:mm:ss"
35838                    || java_fmt == "yyyy-MM-dd"
35839                    || e.format == "yyyy-MM-dd HH:mm:ss"
35840                    || e.format == "yyyy-MM-dd"
35841                {
35842                    self.write_keyword("CAST");
35843                    self.write("(");
35844                    self.generate_expression(&e.this)?;
35845                    self.write(" ");
35846                    self.write_keyword("AS TIMESTAMP");
35847                    self.write(")");
35848                } else {
35849                    // CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS TIMESTAMP)
35850                    self.write_keyword("CAST");
35851                    self.write("(");
35852                    self.write_keyword("FROM_UNIXTIME");
35853                    self.write("(");
35854                    self.write_keyword("UNIX_TIMESTAMP");
35855                    self.write("(");
35856                    self.generate_expression(&e.this)?;
35857                    self.write(", '");
35858                    self.write(&java_fmt);
35859                    self.write("')");
35860                    self.write(") ");
35861                    self.write_keyword("AS TIMESTAMP");
35862                    self.write(")");
35863                }
35864            }
35865            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
35866                // Spark: TO_TIMESTAMP(value, java_format)
35867                let java_fmt = to_java(&e.format);
35868                self.write_keyword("TO_TIMESTAMP");
35869                self.write("(");
35870                self.generate_expression(&e.this)?;
35871                self.write(", '");
35872                self.write(&java_fmt);
35873                self.write("')");
35874            }
35875            Some(DialectType::MySQL) => {
35876                // MySQL: STR_TO_DATE(value, format)
35877                let mut fmt = to_strftime(&e.format);
35878                // MySQL uses %e for non-padded day, %T for %H:%M:%S
35879                fmt = fmt.replace("%-d", "%e");
35880                fmt = fmt.replace("%-m", "%c");
35881                fmt = fmt.replace("%H:%M:%S", "%T");
35882                self.write_keyword("STR_TO_DATE");
35883                self.write("(");
35884                self.generate_expression(&e.this)?;
35885                self.write(", '");
35886                self.write(&fmt);
35887                self.write("')");
35888            }
35889            Some(DialectType::Drill) => {
35890                // Drill: TO_TIMESTAMP(value, java_format) with T quoted in single quotes
35891                let java_fmt = to_java(&e.format);
35892                // Drill quotes literal T character: T -> ''T'' (double-quoted within SQL string literal)
35893                let java_fmt = java_fmt.replace('T', "''T''");
35894                self.write_keyword("TO_TIMESTAMP");
35895                self.write("(");
35896                self.generate_expression(&e.this)?;
35897                self.write(", '");
35898                self.write(&java_fmt);
35899                self.write("')");
35900            }
35901            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
35902                // Presto: DATE_PARSE(value, strftime_format)
35903                let mut fmt = to_strftime(&e.format);
35904                // Presto uses %e for non-padded day, %T for %H:%M:%S
35905                fmt = fmt.replace("%-d", "%e");
35906                fmt = fmt.replace("%-m", "%c");
35907                fmt = fmt.replace("%H:%M:%S", "%T");
35908                self.write_keyword("DATE_PARSE");
35909                self.write("(");
35910                self.generate_expression(&e.this)?;
35911                self.write(", '");
35912                self.write(&fmt);
35913                self.write("')");
35914            }
35915            Some(DialectType::DuckDB) => {
35916                // DuckDB: STRPTIME(value, strftime_format)
35917                let fmt = to_strftime(&e.format);
35918                self.write_keyword("STRPTIME");
35919                self.write("(");
35920                self.generate_expression(&e.this)?;
35921                self.write(", '");
35922                self.write(&fmt);
35923                self.write("')");
35924            }
35925            Some(DialectType::PostgreSQL)
35926            | Some(DialectType::Redshift)
35927            | Some(DialectType::Materialize) => {
35928                // PostgreSQL/Redshift/Materialize: TO_TIMESTAMP(value, pg_format)
35929                let pg_fmt = to_pg(&e.format);
35930                self.write_keyword("TO_TIMESTAMP");
35931                self.write("(");
35932                self.generate_expression(&e.this)?;
35933                self.write(", '");
35934                self.write(&pg_fmt);
35935                self.write("')");
35936            }
35937            Some(DialectType::Oracle) => {
35938                // Oracle: TO_TIMESTAMP(value, pg_format)
35939                let pg_fmt = to_pg(&e.format);
35940                self.write_keyword("TO_TIMESTAMP");
35941                self.write("(");
35942                self.generate_expression(&e.this)?;
35943                self.write(", '");
35944                self.write(&pg_fmt);
35945                self.write("')");
35946            }
35947            Some(DialectType::Snowflake) => {
35948                // Snowflake: TO_TIMESTAMP(value, format) - native format
35949                self.write_keyword("TO_TIMESTAMP");
35950                self.write("(");
35951                self.generate_expression(&e.this)?;
35952                self.write(", '");
35953                self.write(&e.format);
35954                self.write("')");
35955            }
35956            _ => {
35957                // Default: STR_TO_TIME(this, format)
35958                self.write_keyword("STR_TO_TIME");
35959                self.write("(");
35960                self.generate_expression(&e.this)?;
35961                self.write(", '");
35962                self.write(&e.format);
35963                self.write("'");
35964                self.write(")");
35965            }
35966        }
35967        Ok(())
35968    }
35969
35970    /// Convert Snowflake normalized format to strftime-style (%Y, %m, etc.)
35971    fn snowflake_format_to_strftime(format: &str) -> String {
35972        let mut result = String::new();
35973        let chars: Vec<char> = format.chars().collect();
35974        let mut i = 0;
35975        while i < chars.len() {
35976            let remaining = &format[i..];
35977            if remaining.starts_with("yyyy") {
35978                result.push_str("%Y");
35979                i += 4;
35980            } else if remaining.starts_with("yy") {
35981                result.push_str("%y");
35982                i += 2;
35983            } else if remaining.starts_with("mmmm") {
35984                result.push_str("%B"); // full month name
35985                i += 4;
35986            } else if remaining.starts_with("mon") {
35987                result.push_str("%b"); // abbreviated month
35988                i += 3;
35989            } else if remaining.starts_with("mm") {
35990                result.push_str("%m");
35991                i += 2;
35992            } else if remaining.starts_with("DD") {
35993                result.push_str("%d");
35994                i += 2;
35995            } else if remaining.starts_with("dy") {
35996                result.push_str("%a"); // abbreviated day name
35997                i += 2;
35998            } else if remaining.starts_with("hh24") {
35999                result.push_str("%H");
36000                i += 4;
36001            } else if remaining.starts_with("hh12") {
36002                result.push_str("%I");
36003                i += 4;
36004            } else if remaining.starts_with("hh") {
36005                result.push_str("%H");
36006                i += 2;
36007            } else if remaining.starts_with("mi") {
36008                result.push_str("%M");
36009                i += 2;
36010            } else if remaining.starts_with("ss") {
36011                result.push_str("%S");
36012                i += 2;
36013            } else if remaining.starts_with("ff") {
36014                // Fractional seconds
36015                result.push_str("%f");
36016                i += 2;
36017                // Skip digits after ff (ff3, ff6, ff9)
36018                while i < chars.len() && chars[i].is_ascii_digit() {
36019                    i += 1;
36020                }
36021            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
36022                result.push_str("%p");
36023                i += 2;
36024            } else if remaining.starts_with("tz") {
36025                result.push_str("%Z");
36026                i += 2;
36027            } else {
36028                result.push(chars[i]);
36029                i += 1;
36030            }
36031        }
36032        result
36033    }
36034
36035    /// Convert Snowflake normalized format to Spark format (Java-style)
36036    fn snowflake_format_to_spark(format: &str) -> String {
36037        let mut result = String::new();
36038        let chars: Vec<char> = format.chars().collect();
36039        let mut i = 0;
36040        while i < chars.len() {
36041            let remaining = &format[i..];
36042            if remaining.starts_with("yyyy") {
36043                result.push_str("yyyy");
36044                i += 4;
36045            } else if remaining.starts_with("yy") {
36046                result.push_str("yy");
36047                i += 2;
36048            } else if remaining.starts_with("mmmm") {
36049                result.push_str("MMMM"); // full month name
36050                i += 4;
36051            } else if remaining.starts_with("mon") {
36052                result.push_str("MMM"); // abbreviated month
36053                i += 3;
36054            } else if remaining.starts_with("mm") {
36055                result.push_str("MM");
36056                i += 2;
36057            } else if remaining.starts_with("DD") {
36058                result.push_str("dd");
36059                i += 2;
36060            } else if remaining.starts_with("dy") {
36061                result.push_str("EEE"); // abbreviated day name
36062                i += 2;
36063            } else if remaining.starts_with("hh24") {
36064                result.push_str("HH");
36065                i += 4;
36066            } else if remaining.starts_with("hh12") {
36067                result.push_str("hh");
36068                i += 4;
36069            } else if remaining.starts_with("hh") {
36070                result.push_str("HH");
36071                i += 2;
36072            } else if remaining.starts_with("mi") {
36073                result.push_str("mm");
36074                i += 2;
36075            } else if remaining.starts_with("ss") {
36076                result.push_str("ss");
36077                i += 2;
36078            } else if remaining.starts_with("ff") {
36079                result.push_str("SSS"); // milliseconds
36080                i += 2;
36081                // Skip digits after ff
36082                while i < chars.len() && chars[i].is_ascii_digit() {
36083                    i += 1;
36084                }
36085            } else if remaining.starts_with("am") || remaining.starts_with("pm") {
36086                result.push_str("a");
36087                i += 2;
36088            } else if remaining.starts_with("tz") {
36089                result.push_str("z");
36090                i += 2;
36091            } else {
36092                result.push(chars[i]);
36093                i += 1;
36094            }
36095        }
36096        result
36097    }
36098
36099    fn generate_str_to_unix(&mut self, e: &StrToUnix) -> Result<()> {
36100        match self.config.dialect {
36101            Some(DialectType::DuckDB) => {
36102                // DuckDB: EPOCH(STRPTIME(value, format))
36103                self.write_keyword("EPOCH");
36104                self.write("(");
36105                self.write_keyword("STRPTIME");
36106                self.write("(");
36107                if let Some(this) = &e.this {
36108                    self.generate_expression(this)?;
36109                }
36110                if let Some(format) = &e.format {
36111                    self.write(", '");
36112                    self.write(format);
36113                    self.write("'");
36114                }
36115                self.write("))");
36116            }
36117            Some(DialectType::Hive) => {
36118                // Hive: UNIX_TIMESTAMP(value, java_format) - convert C fmt to Java
36119                self.write_keyword("UNIX_TIMESTAMP");
36120                self.write("(");
36121                if let Some(this) = &e.this {
36122                    self.generate_expression(this)?;
36123                }
36124                if let Some(format) = &e.format {
36125                    let java_fmt = Self::strftime_to_java_format(format);
36126                    if java_fmt != "yyyy-MM-dd HH:mm:ss" {
36127                        self.write(", '");
36128                        self.write(&java_fmt);
36129                        self.write("'");
36130                    }
36131                }
36132                self.write(")");
36133            }
36134            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36135                // Doris/StarRocks: UNIX_TIMESTAMP(value, format) - C format
36136                self.write_keyword("UNIX_TIMESTAMP");
36137                self.write("(");
36138                if let Some(this) = &e.this {
36139                    self.generate_expression(this)?;
36140                }
36141                if let Some(format) = &e.format {
36142                    self.write(", '");
36143                    self.write(format);
36144                    self.write("'");
36145                }
36146                self.write(")");
36147            }
36148            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36149                // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(value AS VARCHAR), c_format)),
36150                //   PARSE_DATETIME(DATE_FORMAT(CAST(value AS TIMESTAMP), c_format), java_format)))
36151                let c_fmt = e.format.as_deref().unwrap_or("%Y-%m-%d %T");
36152                let java_fmt = Self::strftime_to_java_format(c_fmt);
36153                self.write_keyword("TO_UNIXTIME");
36154                self.write("(");
36155                self.write_keyword("COALESCE");
36156                self.write("(");
36157                self.write_keyword("TRY");
36158                self.write("(");
36159                self.write_keyword("DATE_PARSE");
36160                self.write("(");
36161                self.write_keyword("CAST");
36162                self.write("(");
36163                if let Some(this) = &e.this {
36164                    self.generate_expression(this)?;
36165                }
36166                self.write(" ");
36167                self.write_keyword("AS VARCHAR");
36168                self.write("), '");
36169                self.write(c_fmt);
36170                self.write("')), ");
36171                self.write_keyword("PARSE_DATETIME");
36172                self.write("(");
36173                self.write_keyword("DATE_FORMAT");
36174                self.write("(");
36175                self.write_keyword("CAST");
36176                self.write("(");
36177                if let Some(this) = &e.this {
36178                    self.generate_expression(this)?;
36179                }
36180                self.write(" ");
36181                self.write_keyword("AS TIMESTAMP");
36182                self.write("), '");
36183                self.write(c_fmt);
36184                self.write("'), '");
36185                self.write(&java_fmt);
36186                self.write("')))");
36187            }
36188            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36189                // Spark: UNIX_TIMESTAMP(value, java_format)
36190                self.write_keyword("UNIX_TIMESTAMP");
36191                self.write("(");
36192                if let Some(this) = &e.this {
36193                    self.generate_expression(this)?;
36194                }
36195                if let Some(format) = &e.format {
36196                    let java_fmt = Self::strftime_to_java_format(format);
36197                    self.write(", '");
36198                    self.write(&java_fmt);
36199                    self.write("'");
36200                }
36201                self.write(")");
36202            }
36203            _ => {
36204                // Default: STR_TO_UNIX(this, format)
36205                self.write_keyword("STR_TO_UNIX");
36206                self.write("(");
36207                if let Some(this) = &e.this {
36208                    self.generate_expression(this)?;
36209                }
36210                if let Some(format) = &e.format {
36211                    self.write(", '");
36212                    self.write(format);
36213                    self.write("'");
36214                }
36215                self.write(")");
36216            }
36217        }
36218        Ok(())
36219    }
36220
36221    fn generate_string_to_array(&mut self, e: &StringToArray) -> Result<()> {
36222        // STRING_TO_ARRAY(this, delimiter, null_string)
36223        self.write_keyword("STRING_TO_ARRAY");
36224        self.write("(");
36225        self.generate_expression(&e.this)?;
36226        if let Some(expression) = &e.expression {
36227            self.write(", ");
36228            self.generate_expression(expression)?;
36229        }
36230        if let Some(null_val) = &e.null {
36231            self.write(", ");
36232            self.generate_expression(null_val)?;
36233        }
36234        self.write(")");
36235        Ok(())
36236    }
36237
36238    fn generate_struct(&mut self, e: &Struct) -> Result<()> {
36239        if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36240            // Snowflake: OBJECT_CONSTRUCT('key', value, 'key', value, ...)
36241            self.write_keyword("OBJECT_CONSTRUCT");
36242            self.write("(");
36243            for (i, (name, expr)) in e.fields.iter().enumerate() {
36244                if i > 0 {
36245                    self.write(", ");
36246                }
36247                if let Some(name) = name {
36248                    self.write("'");
36249                    self.write(name);
36250                    self.write("'");
36251                    self.write(", ");
36252                } else {
36253                    self.write("'_");
36254                    self.write(&i.to_string());
36255                    self.write("'");
36256                    self.write(", ");
36257                }
36258                self.generate_expression(expr)?;
36259            }
36260            self.write(")");
36261        } else if self.config.struct_curly_brace_notation {
36262            // DuckDB-style: {'key': value, ...}
36263            self.write("{");
36264            for (i, (name, expr)) in e.fields.iter().enumerate() {
36265                if i > 0 {
36266                    self.write(", ");
36267                }
36268                if let Some(name) = name {
36269                    // Quote the key as a string literal
36270                    self.write("'");
36271                    self.write(name);
36272                    self.write("'");
36273                    self.write(": ");
36274                } else {
36275                    // Unnamed field: use positional key
36276                    self.write("'_");
36277                    self.write(&i.to_string());
36278                    self.write("'");
36279                    self.write(": ");
36280                }
36281                self.generate_expression(expr)?;
36282            }
36283            self.write("}");
36284        } else {
36285            // Standard SQL struct notation
36286            // BigQuery/Spark/Databricks use: STRUCT(value AS name, ...)
36287            // Others (Presto etc.) use: STRUCT(name AS value, ...) or ROW(value, ...)
36288            let value_as_name = matches!(
36289                self.config.dialect,
36290                Some(DialectType::BigQuery)
36291                    | Some(DialectType::Spark)
36292                    | Some(DialectType::Databricks)
36293                    | Some(DialectType::Hive)
36294            );
36295            self.write_keyword("STRUCT");
36296            self.write("(");
36297            for (i, (name, expr)) in e.fields.iter().enumerate() {
36298                if i > 0 {
36299                    self.write(", ");
36300                }
36301                if let Some(name) = name {
36302                    if value_as_name {
36303                        // STRUCT(value AS name)
36304                        self.generate_expression(expr)?;
36305                        self.write_space();
36306                        self.write_keyword("AS");
36307                        self.write_space();
36308                        // Quote name if it contains spaces or special chars
36309                        let needs_quoting = name.contains(' ') || name.contains('-');
36310                        if needs_quoting {
36311                            if matches!(
36312                                self.config.dialect,
36313                                Some(DialectType::Spark)
36314                                    | Some(DialectType::Databricks)
36315                                    | Some(DialectType::Hive)
36316                            ) {
36317                                self.write("`");
36318                                self.write(name);
36319                                self.write("`");
36320                            } else {
36321                                self.write(name);
36322                            }
36323                        } else {
36324                            self.write(name);
36325                        }
36326                    } else {
36327                        // STRUCT(name AS value)
36328                        self.write(name);
36329                        self.write_space();
36330                        self.write_keyword("AS");
36331                        self.write_space();
36332                        self.generate_expression(expr)?;
36333                    }
36334                } else {
36335                    self.generate_expression(expr)?;
36336                }
36337            }
36338            self.write(")");
36339        }
36340        Ok(())
36341    }
36342
36343    fn generate_stuff(&mut self, e: &Stuff) -> Result<()> {
36344        // STUFF(this, start, length, expression)
36345        self.write_keyword("STUFF");
36346        self.write("(");
36347        self.generate_expression(&e.this)?;
36348        if let Some(start) = &e.start {
36349            self.write(", ");
36350            self.generate_expression(start)?;
36351        }
36352        if let Some(length) = e.length {
36353            self.write(", ");
36354            self.write(&length.to_string());
36355        }
36356        self.write(", ");
36357        self.generate_expression(&e.expression)?;
36358        self.write(")");
36359        Ok(())
36360    }
36361
36362    fn generate_substring_index(&mut self, e: &SubstringIndex) -> Result<()> {
36363        // SUBSTRING_INDEX(this, delimiter, count)
36364        self.write_keyword("SUBSTRING_INDEX");
36365        self.write("(");
36366        self.generate_expression(&e.this)?;
36367        if let Some(delimiter) = &e.delimiter {
36368            self.write(", ");
36369            self.generate_expression(delimiter)?;
36370        }
36371        if let Some(count) = &e.count {
36372            self.write(", ");
36373            self.generate_expression(count)?;
36374        }
36375        self.write(")");
36376        Ok(())
36377    }
36378
36379    fn generate_summarize(&mut self, e: &Summarize) -> Result<()> {
36380        // SUMMARIZE [TABLE] this
36381        self.write_keyword("SUMMARIZE");
36382        if e.table.is_some() {
36383            self.write_space();
36384            self.write_keyword("TABLE");
36385        }
36386        self.write_space();
36387        self.generate_expression(&e.this)?;
36388        Ok(())
36389    }
36390
36391    fn generate_systimestamp(&mut self, _e: &Systimestamp) -> Result<()> {
36392        // SYSTIMESTAMP
36393        self.write_keyword("SYSTIMESTAMP");
36394        Ok(())
36395    }
36396
36397    fn generate_table_alias(&mut self, e: &TableAlias) -> Result<()> {
36398        // alias (columns...)
36399        if let Some(this) = &e.this {
36400            self.generate_expression(this)?;
36401        }
36402        if !e.columns.is_empty() {
36403            self.write("(");
36404            for (i, col) in e.columns.iter().enumerate() {
36405                if i > 0 {
36406                    self.write(", ");
36407                }
36408                self.generate_expression(col)?;
36409            }
36410            self.write(")");
36411        }
36412        Ok(())
36413    }
36414
36415    fn generate_table_from_rows(&mut self, e: &TableFromRows) -> Result<()> {
36416        // TABLE(this) [AS alias]
36417        self.write_keyword("TABLE");
36418        self.write("(");
36419        self.generate_expression(&e.this)?;
36420        self.write(")");
36421        if let Some(alias) = &e.alias {
36422            self.write_space();
36423            self.write_keyword("AS");
36424            self.write_space();
36425            self.write(alias);
36426        }
36427        Ok(())
36428    }
36429
36430    fn generate_rows_from(&mut self, e: &RowsFrom) -> Result<()> {
36431        // ROWS FROM (func1(...) AS alias1(...), func2(...) AS alias2(...)) [WITH ORDINALITY] [AS alias(...)]
36432        self.write_keyword("ROWS FROM");
36433        self.write(" (");
36434        for (i, expr) in e.expressions.iter().enumerate() {
36435            if i > 0 {
36436                self.write(", ");
36437            }
36438            // Each expression is either:
36439            // - A plain function (no alias)
36440            // - A Tuple(function, TableAlias) for: FUNC() AS alias(col type, ...)
36441            match expr {
36442                Expression::Tuple(tuple) if tuple.expressions.len() == 2 => {
36443                    // First element is the function, second is the TableAlias
36444                    self.generate_expression(&tuple.expressions[0])?;
36445                    self.write_space();
36446                    self.write_keyword("AS");
36447                    self.write_space();
36448                    self.generate_expression(&tuple.expressions[1])?;
36449                }
36450                _ => {
36451                    self.generate_expression(expr)?;
36452                }
36453            }
36454        }
36455        self.write(")");
36456        if e.ordinality {
36457            self.write_space();
36458            self.write_keyword("WITH ORDINALITY");
36459        }
36460        if let Some(alias) = &e.alias {
36461            self.write_space();
36462            self.write_keyword("AS");
36463            self.write_space();
36464            self.generate_expression(alias)?;
36465        }
36466        Ok(())
36467    }
36468
36469    fn generate_table_sample(&mut self, e: &TableSample) -> Result<()> {
36470        use crate::dialects::DialectType;
36471
36472        // New wrapper pattern: expression + Sample struct
36473        if let (Some(this), Some(sample)) = (&e.this, &e.sample) {
36474            // For alias_post_tablesample dialects (Spark, Hive, Oracle): output base expr, TABLESAMPLE, then alias
36475            if self.config.alias_post_tablesample {
36476                // Handle Subquery with alias and Alias wrapper
36477                if let Expression::Subquery(ref s) = **this {
36478                    if let Some(ref alias) = s.alias {
36479                        // Create a clone without alias for output
36480                        let mut subquery_no_alias = (**s).clone();
36481                        subquery_no_alias.alias = None;
36482                        subquery_no_alias.column_aliases = Vec::new();
36483                        self.generate_expression(&Expression::Subquery(Box::new(
36484                            subquery_no_alias,
36485                        )))?;
36486                        self.write_space();
36487                        self.write_keyword(self.config.tablesample_keywords);
36488                        self.generate_sample_body(sample)?;
36489                        if let Some(ref seed) = sample.seed {
36490                            self.write_space();
36491                            let use_seed = sample.use_seed_keyword
36492                                && !matches!(
36493                                    self.config.dialect,
36494                                    Some(crate::dialects::DialectType::Databricks)
36495                                        | Some(crate::dialects::DialectType::Spark)
36496                                );
36497                            if use_seed {
36498                                self.write_keyword("SEED");
36499                            } else {
36500                                self.write_keyword("REPEATABLE");
36501                            }
36502                            self.write(" (");
36503                            self.generate_expression(seed)?;
36504                            self.write(")");
36505                        }
36506                        self.write_space();
36507                        self.write_keyword("AS");
36508                        self.write_space();
36509                        self.generate_identifier(alias)?;
36510                        return Ok(());
36511                    }
36512                } else if let Expression::Alias(ref a) = **this {
36513                    // Output the base expression without alias
36514                    self.generate_expression(&a.this)?;
36515                    self.write_space();
36516                    self.write_keyword(self.config.tablesample_keywords);
36517                    self.generate_sample_body(sample)?;
36518                    if let Some(ref seed) = sample.seed {
36519                        self.write_space();
36520                        let use_seed = sample.use_seed_keyword
36521                            && !matches!(
36522                                self.config.dialect,
36523                                Some(crate::dialects::DialectType::Databricks)
36524                                    | Some(crate::dialects::DialectType::Spark)
36525                            );
36526                        if use_seed {
36527                            self.write_keyword("SEED");
36528                        } else {
36529                            self.write_keyword("REPEATABLE");
36530                        }
36531                        self.write(" (");
36532                        self.generate_expression(seed)?;
36533                        self.write(")");
36534                    }
36535                    // Output alias after TABLESAMPLE
36536                    self.write_space();
36537                    self.write_keyword("AS");
36538                    self.write_space();
36539                    self.generate_identifier(&a.alias)?;
36540                    return Ok(());
36541                }
36542            }
36543            // Default: generate wrapped expression first, then TABLESAMPLE
36544            self.generate_expression(this)?;
36545            self.write_space();
36546            self.write_keyword(self.config.tablesample_keywords);
36547            self.generate_sample_body(sample)?;
36548            // Seed for table-level sample
36549            if let Some(ref seed) = sample.seed {
36550                self.write_space();
36551                // Databricks uses REPEATABLE, not SEED
36552                let use_seed = sample.use_seed_keyword
36553                    && !matches!(
36554                        self.config.dialect,
36555                        Some(crate::dialects::DialectType::Databricks)
36556                            | Some(crate::dialects::DialectType::Spark)
36557                    );
36558                if use_seed {
36559                    self.write_keyword("SEED");
36560                } else {
36561                    self.write_keyword("REPEATABLE");
36562                }
36563                self.write(" (");
36564                self.generate_expression(seed)?;
36565                self.write(")");
36566            }
36567            return Ok(());
36568        }
36569
36570        // Legacy pattern: TABLESAMPLE [method] (expressions) or TABLESAMPLE method BUCKET numerator OUT OF denominator
36571        self.write_keyword(self.config.tablesample_keywords);
36572        if let Some(method) = &e.method {
36573            self.write_space();
36574            self.write_keyword(method);
36575        } else if matches!(self.config.dialect, Some(DialectType::Snowflake)) {
36576            // Snowflake defaults to BERNOULLI when no method is specified
36577            self.write_space();
36578            self.write_keyword("BERNOULLI");
36579        }
36580        if let (Some(numerator), Some(denominator)) = (&e.bucket_numerator, &e.bucket_denominator) {
36581            self.write_space();
36582            self.write_keyword("BUCKET");
36583            self.write_space();
36584            self.generate_expression(numerator)?;
36585            self.write_space();
36586            self.write_keyword("OUT OF");
36587            self.write_space();
36588            self.generate_expression(denominator)?;
36589            if let Some(field) = &e.bucket_field {
36590                self.write_space();
36591                self.write_keyword("ON");
36592                self.write_space();
36593                self.generate_expression(field)?;
36594            }
36595        } else if !e.expressions.is_empty() {
36596            self.write(" (");
36597            for (i, expr) in e.expressions.iter().enumerate() {
36598                if i > 0 {
36599                    self.write(", ");
36600                }
36601                self.generate_expression(expr)?;
36602            }
36603            self.write(")");
36604        } else if let Some(percent) = &e.percent {
36605            self.write(" (");
36606            self.generate_expression(percent)?;
36607            self.write_space();
36608            self.write_keyword("PERCENT");
36609            self.write(")");
36610        }
36611        Ok(())
36612    }
36613
36614    fn generate_tag(&mut self, e: &Tag) -> Result<()> {
36615        // [prefix]this[postfix]
36616        if let Some(prefix) = &e.prefix {
36617            self.generate_expression(prefix)?;
36618        }
36619        if let Some(this) = &e.this {
36620            self.generate_expression(this)?;
36621        }
36622        if let Some(postfix) = &e.postfix {
36623            self.generate_expression(postfix)?;
36624        }
36625        Ok(())
36626    }
36627
36628    fn generate_tags(&mut self, e: &Tags) -> Result<()> {
36629        // TAG (expressions)
36630        self.write_keyword("TAG");
36631        self.write(" (");
36632        for (i, expr) in e.expressions.iter().enumerate() {
36633            if i > 0 {
36634                self.write(", ");
36635            }
36636            self.generate_expression(expr)?;
36637        }
36638        self.write(")");
36639        Ok(())
36640    }
36641
36642    fn generate_temporary_property(&mut self, e: &TemporaryProperty) -> Result<()> {
36643        // TEMPORARY or TEMP or [this] TEMPORARY
36644        if let Some(this) = &e.this {
36645            self.generate_expression(this)?;
36646            self.write_space();
36647        }
36648        self.write_keyword("TEMPORARY");
36649        Ok(())
36650    }
36651
36652    /// Generate a Time function expression
36653    /// For most dialects: TIME('value')
36654    fn generate_time_func(&mut self, e: &UnaryFunc) -> Result<()> {
36655        // Standard: TIME(value)
36656        self.write_keyword("TIME");
36657        self.write("(");
36658        self.generate_expression(&e.this)?;
36659        self.write(")");
36660        Ok(())
36661    }
36662
36663    fn generate_time_add(&mut self, e: &TimeAdd) -> Result<()> {
36664        // TIME_ADD(this, expression, unit)
36665        self.write_keyword("TIME_ADD");
36666        self.write("(");
36667        self.generate_expression(&e.this)?;
36668        self.write(", ");
36669        self.generate_expression(&e.expression)?;
36670        if let Some(unit) = &e.unit {
36671            self.write(", ");
36672            self.write_keyword(unit);
36673        }
36674        self.write(")");
36675        Ok(())
36676    }
36677
36678    fn generate_time_diff(&mut self, e: &TimeDiff) -> Result<()> {
36679        // TIME_DIFF(this, expression, unit)
36680        self.write_keyword("TIME_DIFF");
36681        self.write("(");
36682        self.generate_expression(&e.this)?;
36683        self.write(", ");
36684        self.generate_expression(&e.expression)?;
36685        if let Some(unit) = &e.unit {
36686            self.write(", ");
36687            self.write_keyword(unit);
36688        }
36689        self.write(")");
36690        Ok(())
36691    }
36692
36693    fn generate_time_from_parts(&mut self, e: &TimeFromParts) -> Result<()> {
36694        // TIME_FROM_PARTS(hour, minute, second, nanosecond)
36695        self.write_keyword("TIME_FROM_PARTS");
36696        self.write("(");
36697        let mut first = true;
36698        if let Some(hour) = &e.hour {
36699            self.generate_expression(hour)?;
36700            first = false;
36701        }
36702        if let Some(minute) = &e.min {
36703            if !first {
36704                self.write(", ");
36705            }
36706            self.generate_expression(minute)?;
36707            first = false;
36708        }
36709        if let Some(second) = &e.sec {
36710            if !first {
36711                self.write(", ");
36712            }
36713            self.generate_expression(second)?;
36714            first = false;
36715        }
36716        if let Some(ns) = &e.nano {
36717            if !first {
36718                self.write(", ");
36719            }
36720            self.generate_expression(ns)?;
36721        }
36722        self.write(")");
36723        Ok(())
36724    }
36725
36726    fn generate_time_slice(&mut self, e: &TimeSlice) -> Result<()> {
36727        // TIME_SLICE(this, expression, unit)
36728        self.write_keyword("TIME_SLICE");
36729        self.write("(");
36730        self.generate_expression(&e.this)?;
36731        self.write(", ");
36732        self.generate_expression(&e.expression)?;
36733        self.write(", ");
36734        self.write_keyword(&e.unit);
36735        self.write(")");
36736        Ok(())
36737    }
36738
36739    fn generate_time_str_to_time(&mut self, e: &TimeStrToTime) -> Result<()> {
36740        // TIME_STR_TO_TIME(this)
36741        self.write_keyword("TIME_STR_TO_TIME");
36742        self.write("(");
36743        self.generate_expression(&e.this)?;
36744        self.write(")");
36745        Ok(())
36746    }
36747
36748    fn generate_time_sub(&mut self, e: &TimeSub) -> Result<()> {
36749        // TIME_SUB(this, expression, unit)
36750        self.write_keyword("TIME_SUB");
36751        self.write("(");
36752        self.generate_expression(&e.this)?;
36753        self.write(", ");
36754        self.generate_expression(&e.expression)?;
36755        if let Some(unit) = &e.unit {
36756            self.write(", ");
36757            self.write_keyword(unit);
36758        }
36759        self.write(")");
36760        Ok(())
36761    }
36762
36763    fn generate_time_to_str(&mut self, e: &TimeToStr) -> Result<()> {
36764        match self.config.dialect {
36765            Some(DialectType::Exasol) => {
36766                // Exasol uses TO_CHAR with Exasol-specific format
36767                self.write_keyword("TO_CHAR");
36768                self.write("(");
36769                self.generate_expression(&e.this)?;
36770                self.write(", '");
36771                self.write(&Self::convert_strptime_to_exasol_format(&e.format));
36772                self.write("'");
36773                self.write(")");
36774            }
36775            Some(DialectType::PostgreSQL)
36776            | Some(DialectType::Redshift)
36777            | Some(DialectType::Materialize) => {
36778                // PostgreSQL/Redshift/Materialize uses TO_CHAR with PG-specific format
36779                self.write_keyword("TO_CHAR");
36780                self.write("(");
36781                self.generate_expression(&e.this)?;
36782                self.write(", '");
36783                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36784                self.write("'");
36785                self.write(")");
36786            }
36787            Some(DialectType::Oracle) => {
36788                // Oracle uses TO_CHAR with PG-like format
36789                self.write_keyword("TO_CHAR");
36790                self.write("(");
36791                self.generate_expression(&e.this)?;
36792                self.write(", '");
36793                self.write(&Self::convert_strptime_to_postgres_format(&e.format));
36794                self.write("'");
36795                self.write(")");
36796            }
36797            Some(DialectType::Drill) => {
36798                // Drill: TO_CHAR with Java format
36799                self.write_keyword("TO_CHAR");
36800                self.write("(");
36801                self.generate_expression(&e.this)?;
36802                self.write(", '");
36803                self.write(&Self::strftime_to_java_format(&e.format));
36804                self.write("'");
36805                self.write(")");
36806            }
36807            Some(DialectType::TSQL) | Some(DialectType::Fabric) => {
36808                // TSQL: FORMAT(value, format) with .NET-style format
36809                self.write_keyword("FORMAT");
36810                self.write("(");
36811                self.generate_expression(&e.this)?;
36812                self.write(", '");
36813                self.write(&Self::strftime_to_tsql_format(&e.format));
36814                self.write("'");
36815                self.write(")");
36816            }
36817            Some(DialectType::DuckDB) => {
36818                // DuckDB: STRFTIME(value, format) - keeps C format
36819                self.write_keyword("STRFTIME");
36820                self.write("(");
36821                self.generate_expression(&e.this)?;
36822                self.write(", '");
36823                self.write(&e.format);
36824                self.write("'");
36825                self.write(")");
36826            }
36827            Some(DialectType::BigQuery) => {
36828                // BigQuery: FORMAT_DATE(format, value) - note swapped arg order
36829                // Normalize: %Y-%m-%d -> %F, %H:%M:%S -> %T
36830                let fmt = e.format.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T");
36831                self.write_keyword("FORMAT_DATE");
36832                self.write("('");
36833                self.write(&fmt);
36834                self.write("', ");
36835                self.generate_expression(&e.this)?;
36836                self.write(")");
36837            }
36838            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks) => {
36839                // Hive/Spark: DATE_FORMAT(value, java_format)
36840                self.write_keyword("DATE_FORMAT");
36841                self.write("(");
36842                self.generate_expression(&e.this)?;
36843                self.write(", '");
36844                self.write(&Self::strftime_to_java_format(&e.format));
36845                self.write("'");
36846                self.write(")");
36847            }
36848            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena) => {
36849                // Presto/Trino: DATE_FORMAT(value, format) - keeps C format
36850                self.write_keyword("DATE_FORMAT");
36851                self.write("(");
36852                self.generate_expression(&e.this)?;
36853                self.write(", '");
36854                self.write(&e.format);
36855                self.write("'");
36856                self.write(")");
36857            }
36858            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
36859                // Doris/StarRocks: DATE_FORMAT(value, format) - keeps C format
36860                self.write_keyword("DATE_FORMAT");
36861                self.write("(");
36862                self.generate_expression(&e.this)?;
36863                self.write(", '");
36864                self.write(&e.format);
36865                self.write("'");
36866                self.write(")");
36867            }
36868            _ => {
36869                // Default: TIME_TO_STR(this, format)
36870                self.write_keyword("TIME_TO_STR");
36871                self.write("(");
36872                self.generate_expression(&e.this)?;
36873                self.write(", '");
36874                self.write(&e.format);
36875                self.write("'");
36876                self.write(")");
36877            }
36878        }
36879        Ok(())
36880    }
36881
36882    fn generate_time_to_unix(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
36883        match self.config.dialect {
36884            Some(DialectType::DuckDB) => {
36885                // DuckDB: EPOCH(x)
36886                self.write_keyword("EPOCH");
36887                self.write("(");
36888                self.generate_expression(&e.this)?;
36889                self.write(")");
36890            }
36891            Some(DialectType::Hive)
36892            | Some(DialectType::Spark)
36893            | Some(DialectType::Databricks)
36894            | Some(DialectType::Doris)
36895            | Some(DialectType::StarRocks)
36896            | Some(DialectType::Drill) => {
36897                // Hive/Spark/Doris/StarRocks/Drill: UNIX_TIMESTAMP(x)
36898                self.write_keyword("UNIX_TIMESTAMP");
36899                self.write("(");
36900                self.generate_expression(&e.this)?;
36901                self.write(")");
36902            }
36903            Some(DialectType::Presto) | Some(DialectType::Trino) => {
36904                // Presto: TO_UNIXTIME(x)
36905                self.write_keyword("TO_UNIXTIME");
36906                self.write("(");
36907                self.generate_expression(&e.this)?;
36908                self.write(")");
36909            }
36910            _ => {
36911                // Default: TIME_TO_UNIX(x)
36912                self.write_keyword("TIME_TO_UNIX");
36913                self.write("(");
36914                self.generate_expression(&e.this)?;
36915                self.write(")");
36916            }
36917        }
36918        Ok(())
36919    }
36920
36921    fn generate_time_str_to_date(&mut self, e: &crate::expressions::UnaryFunc) -> Result<()> {
36922        match self.config.dialect {
36923            Some(DialectType::Hive) => {
36924                // Hive: TO_DATE(x)
36925                self.write_keyword("TO_DATE");
36926                self.write("(");
36927                self.generate_expression(&e.this)?;
36928                self.write(")");
36929            }
36930            _ => {
36931                // Default: TIME_STR_TO_DATE(x)
36932                self.write_keyword("TIME_STR_TO_DATE");
36933                self.write("(");
36934                self.generate_expression(&e.this)?;
36935                self.write(")");
36936            }
36937        }
36938        Ok(())
36939    }
36940
36941    fn generate_time_trunc(&mut self, e: &TimeTrunc) -> Result<()> {
36942        // TIME_TRUNC(this, unit)
36943        self.write_keyword("TIME_TRUNC");
36944        self.write("(");
36945        self.generate_expression(&e.this)?;
36946        self.write(", ");
36947        self.write_keyword(&e.unit);
36948        self.write(")");
36949        Ok(())
36950    }
36951
36952    fn generate_time_unit(&mut self, e: &TimeUnit) -> Result<()> {
36953        // Just output the unit name
36954        if let Some(unit) = &e.unit {
36955            self.write_keyword(unit);
36956        }
36957        Ok(())
36958    }
36959
36960    /// Generate a Timestamp function expression
36961    /// For Exasol: {ts'value'} -> TO_TIMESTAMP('value')
36962    /// For other dialects: TIMESTAMP('value')
36963    fn generate_timestamp_func(&mut self, e: &TimestampFunc) -> Result<()> {
36964        use crate::dialects::DialectType;
36965        use crate::expressions::Literal;
36966
36967        match self.config.dialect {
36968            // Exasol uses TO_TIMESTAMP for Timestamp expressions
36969            Some(DialectType::Exasol) => {
36970                self.write_keyword("TO_TIMESTAMP");
36971                self.write("(");
36972                // Extract the string value from the expression if it's a string literal
36973                if let Some(this) = &e.this {
36974                    match this.as_ref() {
36975                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36976                            let Literal::String(s) = lit.as_ref() else {
36977                                unreachable!()
36978                            };
36979                            self.write("'");
36980                            self.write(s);
36981                            self.write("'");
36982                        }
36983                        _ => {
36984                            self.generate_expression(this)?;
36985                        }
36986                    }
36987                }
36988                self.write(")");
36989            }
36990            // Standard: TIMESTAMP(value) or TIMESTAMP(value, zone)
36991            _ => {
36992                self.write_keyword("TIMESTAMP");
36993                self.write("(");
36994                if let Some(this) = &e.this {
36995                    self.generate_expression(this)?;
36996                }
36997                if let Some(zone) = &e.zone {
36998                    self.write(", ");
36999                    self.generate_expression(zone)?;
37000                }
37001                self.write(")");
37002            }
37003        }
37004        Ok(())
37005    }
37006
37007    fn generate_timestamp_add(&mut self, e: &TimestampAdd) -> Result<()> {
37008        // TIMESTAMP_ADD(this, expression, unit)
37009        self.write_keyword("TIMESTAMP_ADD");
37010        self.write("(");
37011        self.generate_expression(&e.this)?;
37012        self.write(", ");
37013        self.generate_expression(&e.expression)?;
37014        if let Some(unit) = &e.unit {
37015            self.write(", ");
37016            self.write_keyword(unit);
37017        }
37018        self.write(")");
37019        Ok(())
37020    }
37021
37022    fn generate_timestamp_diff(&mut self, e: &TimestampDiff) -> Result<()> {
37023        // TIMESTAMP_DIFF(this, expression, unit)
37024        self.write_keyword("TIMESTAMP_DIFF");
37025        self.write("(");
37026        self.generate_expression(&e.this)?;
37027        self.write(", ");
37028        self.generate_expression(&e.expression)?;
37029        if let Some(unit) = &e.unit {
37030            self.write(", ");
37031            self.write_keyword(unit);
37032        }
37033        self.write(")");
37034        Ok(())
37035    }
37036
37037    fn generate_timestamp_from_parts(&mut self, e: &TimestampFromParts) -> Result<()> {
37038        // TIMESTAMP_FROM_PARTS(this, expression)
37039        self.write_keyword("TIMESTAMP_FROM_PARTS");
37040        self.write("(");
37041        if let Some(this) = &e.this {
37042            self.generate_expression(this)?;
37043        }
37044        if let Some(expression) = &e.expression {
37045            self.write(", ");
37046            self.generate_expression(expression)?;
37047        }
37048        if let Some(zone) = &e.zone {
37049            self.write(", ");
37050            self.generate_expression(zone)?;
37051        }
37052        if let Some(milli) = &e.milli {
37053            self.write(", ");
37054            self.generate_expression(milli)?;
37055        }
37056        self.write(")");
37057        Ok(())
37058    }
37059
37060    fn generate_timestamp_sub(&mut self, e: &TimestampSub) -> Result<()> {
37061        // TIMESTAMP_SUB(this, INTERVAL expression unit)
37062        self.write_keyword("TIMESTAMP_SUB");
37063        self.write("(");
37064        self.generate_expression(&e.this)?;
37065        self.write(", ");
37066        self.write_keyword("INTERVAL");
37067        self.write_space();
37068        self.generate_expression(&e.expression)?;
37069        if let Some(unit) = &e.unit {
37070            self.write_space();
37071            self.write_keyword(unit);
37072        }
37073        self.write(")");
37074        Ok(())
37075    }
37076
37077    fn generate_timestamp_tz_from_parts(&mut self, e: &TimestampTzFromParts) -> Result<()> {
37078        // TIMESTAMP_TZ_FROM_PARTS(...)
37079        self.write_keyword("TIMESTAMP_TZ_FROM_PARTS");
37080        self.write("(");
37081        if let Some(zone) = &e.zone {
37082            self.generate_expression(zone)?;
37083        }
37084        self.write(")");
37085        Ok(())
37086    }
37087
37088    fn generate_to_binary(&mut self, e: &ToBinary) -> Result<()> {
37089        // TO_BINARY(this, [format])
37090        self.write_keyword("TO_BINARY");
37091        self.write("(");
37092        self.generate_expression(&e.this)?;
37093        if let Some(format) = &e.format {
37094            self.write(", '");
37095            self.write(format);
37096            self.write("'");
37097        }
37098        self.write(")");
37099        Ok(())
37100    }
37101
37102    fn generate_to_boolean(&mut self, e: &ToBoolean) -> Result<()> {
37103        // TO_BOOLEAN(this)
37104        self.write_keyword("TO_BOOLEAN");
37105        self.write("(");
37106        self.generate_expression(&e.this)?;
37107        self.write(")");
37108        Ok(())
37109    }
37110
37111    fn generate_to_char(&mut self, e: &ToChar) -> Result<()> {
37112        // TO_CHAR(this, [format], [nlsparam])
37113        self.write_keyword("TO_CHAR");
37114        self.write("(");
37115        self.generate_expression(&e.this)?;
37116        if let Some(format) = &e.format {
37117            self.write(", '");
37118            self.write(format);
37119            self.write("'");
37120        }
37121        if let Some(nlsparam) = &e.nlsparam {
37122            self.write(", ");
37123            self.generate_expression(nlsparam)?;
37124        }
37125        self.write(")");
37126        Ok(())
37127    }
37128
37129    fn generate_to_decfloat(&mut self, e: &ToDecfloat) -> Result<()> {
37130        // TO_DECFLOAT(this, [format])
37131        self.write_keyword("TO_DECFLOAT");
37132        self.write("(");
37133        self.generate_expression(&e.this)?;
37134        if let Some(format) = &e.format {
37135            self.write(", '");
37136            self.write(format);
37137            self.write("'");
37138        }
37139        self.write(")");
37140        Ok(())
37141    }
37142
37143    fn generate_to_double(&mut self, e: &ToDouble) -> Result<()> {
37144        // TO_DOUBLE(this, [format])
37145        self.write_keyword("TO_DOUBLE");
37146        self.write("(");
37147        self.generate_expression(&e.this)?;
37148        if let Some(format) = &e.format {
37149            self.write(", '");
37150            self.write(format);
37151            self.write("'");
37152        }
37153        self.write(")");
37154        Ok(())
37155    }
37156
37157    fn generate_to_file(&mut self, e: &ToFile) -> Result<()> {
37158        // TO_FILE(this, path)
37159        self.write_keyword("TO_FILE");
37160        self.write("(");
37161        self.generate_expression(&e.this)?;
37162        if let Some(path) = &e.path {
37163            self.write(", ");
37164            self.generate_expression(path)?;
37165        }
37166        self.write(")");
37167        Ok(())
37168    }
37169
37170    fn generate_to_number(&mut self, e: &ToNumber) -> Result<()> {
37171        // TO_NUMBER or TRY_TO_NUMBER (this, [format], [precision], [scale])
37172        // If safe flag is set, output TRY_TO_NUMBER
37173        let is_safe = e.safe.is_some();
37174        if is_safe {
37175            self.write_keyword("TRY_TO_NUMBER");
37176        } else {
37177            self.write_keyword("TO_NUMBER");
37178        }
37179        self.write("(");
37180        self.generate_expression(&e.this)?;
37181        let precision_is_snowflake_default = e.precision.is_none()
37182            || matches!(
37183                e.precision.as_deref(),
37184                Some(Expression::Literal(lit))
37185                    if matches!(lit.as_ref(), Literal::Number(n) if n == "0")
37186            );
37187        let is_snowflake_default_precision =
37188            matches!(self.config.dialect, Some(DialectType::Snowflake))
37189                && e.nlsparam.is_none()
37190                && e.scale.is_none()
37191                && matches!(
37192                    e.format.as_deref(),
37193                    Some(Expression::Literal(lit))
37194                        if matches!(lit.as_ref(), Literal::Number(n) if n == "38")
37195                )
37196                && precision_is_snowflake_default;
37197
37198        if !is_snowflake_default_precision {
37199            if let Some(format) = &e.format {
37200                self.write(", ");
37201                self.generate_expression(format)?;
37202            }
37203            if let Some(nlsparam) = &e.nlsparam {
37204                self.write(", ");
37205                self.generate_expression(nlsparam)?;
37206            }
37207            if let Some(precision) = &e.precision {
37208                self.write(", ");
37209                self.generate_expression(precision)?;
37210            }
37211            if let Some(scale) = &e.scale {
37212                self.write(", ");
37213                self.generate_expression(scale)?;
37214            }
37215        }
37216        self.write(")");
37217        Ok(())
37218    }
37219
37220    fn generate_to_table_property(&mut self, e: &ToTableProperty) -> Result<()> {
37221        // TO_TABLE this
37222        self.write_keyword("TO_TABLE");
37223        self.write_space();
37224        self.generate_expression(&e.this)?;
37225        Ok(())
37226    }
37227
37228    fn generate_transaction(&mut self, e: &Transaction) -> Result<()> {
37229        // Check mark to determine the format
37230        let mark_text = e.mark.as_ref().map(|m| match m.as_ref() {
37231            Expression::Identifier(id) => id.name.clone(),
37232            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
37233                let Literal::String(s) = lit.as_ref() else {
37234                    unreachable!()
37235                };
37236                s.clone()
37237            }
37238            _ => String::new(),
37239        });
37240
37241        let is_start = mark_text.as_ref().map_or(false, |s| s == "START");
37242        let has_transaction_keyword = mark_text.as_ref().map_or(false, |s| s == "TRANSACTION");
37243        let has_with_mark = e.mark.as_ref().map_or(false, |m| {
37244            matches!(m.as_ref(), Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
37245        });
37246
37247        // For Presto/Trino: always use START TRANSACTION
37248        let use_start_transaction = matches!(
37249            self.config.dialect,
37250            Some(DialectType::Presto) | Some(DialectType::Trino) | Some(DialectType::Athena)
37251        );
37252        // For most dialects: strip TRANSACTION keyword
37253        let strip_transaction = matches!(
37254            self.config.dialect,
37255            Some(DialectType::Snowflake)
37256                | Some(DialectType::PostgreSQL)
37257                | Some(DialectType::Redshift)
37258                | Some(DialectType::MySQL)
37259                | Some(DialectType::Hive)
37260                | Some(DialectType::Spark)
37261                | Some(DialectType::Databricks)
37262                | Some(DialectType::DuckDB)
37263                | Some(DialectType::Oracle)
37264                | Some(DialectType::Doris)
37265                | Some(DialectType::StarRocks)
37266                | Some(DialectType::Materialize)
37267                | Some(DialectType::ClickHouse)
37268        );
37269
37270        if is_start || use_start_transaction {
37271            // START TRANSACTION [modes]
37272            self.write_keyword("START TRANSACTION");
37273            if let Some(modes) = &e.modes {
37274                self.write_space();
37275                self.generate_expression(modes)?;
37276            }
37277        } else {
37278            // BEGIN [DEFERRED|IMMEDIATE|EXCLUSIVE] [TRANSACTION] [transaction_name] [WITH MARK 'desc']
37279            self.write_keyword("BEGIN");
37280
37281            // Check if `this` is a transaction kind (DEFERRED/IMMEDIATE/EXCLUSIVE)
37282            let is_kind = e.this.as_ref().map_or(false, |t| {
37283                if let Expression::Identifier(id) = t.as_ref() {
37284                    id.name.eq_ignore_ascii_case("DEFERRED")
37285                        || id.name.eq_ignore_ascii_case("IMMEDIATE")
37286                        || id.name.eq_ignore_ascii_case("EXCLUSIVE")
37287                } else {
37288                    false
37289                }
37290            });
37291
37292            // Output kind before TRANSACTION keyword
37293            if is_kind {
37294                if let Some(this) = &e.this {
37295                    self.write_space();
37296                    if let Expression::Identifier(id) = this.as_ref() {
37297                        self.write_keyword(&id.name);
37298                    }
37299                }
37300            }
37301
37302            // Output TRANSACTION keyword if it was present and target supports it
37303            if (has_transaction_keyword || has_with_mark) && !strip_transaction {
37304                self.write_space();
37305                self.write_keyword("TRANSACTION");
37306            }
37307
37308            // Output transaction name (not kind)
37309            if !is_kind {
37310                if let Some(this) = &e.this {
37311                    self.write_space();
37312                    self.generate_expression(this)?;
37313                }
37314            }
37315
37316            // Output WITH MARK 'description' for TSQL
37317            if has_with_mark {
37318                self.write_space();
37319                self.write_keyword("WITH MARK");
37320                if let Some(Expression::Literal(lit)) = e.mark.as_deref() {
37321                    if let Literal::String(desc) = lit.as_ref() {
37322                        if !desc.is_empty() {
37323                            self.write_space();
37324                            self.write(&format!("'{}'", desc));
37325                        }
37326                    }
37327                }
37328            }
37329
37330            // Output modes (isolation levels, etc.)
37331            if let Some(modes) = &e.modes {
37332                self.write_space();
37333                self.generate_expression(modes)?;
37334            }
37335        }
37336        Ok(())
37337    }
37338
37339    fn generate_transform(&mut self, e: &Transform) -> Result<()> {
37340        // TRANSFORM(this, expression)
37341        self.write_keyword("TRANSFORM");
37342        self.write("(");
37343        self.generate_expression(&e.this)?;
37344        self.write(", ");
37345        self.generate_expression(&e.expression)?;
37346        self.write(")");
37347        Ok(())
37348    }
37349
37350    fn generate_transform_model_property(&mut self, e: &TransformModelProperty) -> Result<()> {
37351        // TRANSFORM(expressions)
37352        self.write_keyword("TRANSFORM");
37353        self.write("(");
37354        if self.config.pretty && !e.expressions.is_empty() {
37355            self.indent_level += 1;
37356            for (i, expr) in e.expressions.iter().enumerate() {
37357                if i > 0 {
37358                    self.write(",");
37359                }
37360                self.write_newline();
37361                self.write_indent();
37362                self.generate_expression(expr)?;
37363            }
37364            self.indent_level -= 1;
37365            self.write_newline();
37366            self.write(")");
37367        } else {
37368            for (i, expr) in e.expressions.iter().enumerate() {
37369                if i > 0 {
37370                    self.write(", ");
37371                }
37372                self.generate_expression(expr)?;
37373            }
37374            self.write(")");
37375        }
37376        Ok(())
37377    }
37378
37379    fn generate_transient_property(&mut self, e: &TransientProperty) -> Result<()> {
37380        use crate::dialects::DialectType;
37381        // TRANSIENT is Snowflake-specific; skip for other dialects
37382        if let Some(this) = &e.this {
37383            self.generate_expression(this)?;
37384            if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
37385                self.write_space();
37386            }
37387        }
37388        if matches!(self.config.dialect, Some(DialectType::Snowflake) | None) {
37389            self.write_keyword("TRANSIENT");
37390        }
37391        Ok(())
37392    }
37393
37394    fn generate_translate(&mut self, e: &Translate) -> Result<()> {
37395        // TRANSLATE(this, from_, to)
37396        self.write_keyword("TRANSLATE");
37397        self.write("(");
37398        self.generate_expression(&e.this)?;
37399        if let Some(from) = &e.from_ {
37400            self.write(", ");
37401            self.generate_expression(from)?;
37402        }
37403        if let Some(to) = &e.to {
37404            self.write(", ");
37405            self.generate_expression(to)?;
37406        }
37407        self.write(")");
37408        Ok(())
37409    }
37410
37411    fn generate_translate_characters(&mut self, e: &TranslateCharacters) -> Result<()> {
37412        // TRANSLATE(this USING expression)
37413        self.write_keyword("TRANSLATE");
37414        self.write("(");
37415        self.generate_expression(&e.this)?;
37416        self.write_space();
37417        self.write_keyword("USING");
37418        self.write_space();
37419        self.generate_expression(&e.expression)?;
37420        if e.with_error.is_some() {
37421            self.write_space();
37422            self.write_keyword("WITH ERROR");
37423        }
37424        self.write(")");
37425        Ok(())
37426    }
37427
37428    fn generate_truncate_table(&mut self, e: &TruncateTable) -> Result<()> {
37429        // TRUNCATE TABLE table1, table2, ...
37430        self.write_keyword("TRUNCATE TABLE");
37431        self.write_space();
37432        for (i, expr) in e.expressions.iter().enumerate() {
37433            if i > 0 {
37434                self.write(", ");
37435            }
37436            self.generate_expression(expr)?;
37437        }
37438        Ok(())
37439    }
37440
37441    fn generate_try_base64_decode_binary(&mut self, e: &TryBase64DecodeBinary) -> Result<()> {
37442        // TRY_BASE64_DECODE_BINARY(this, [alphabet])
37443        self.write_keyword("TRY_BASE64_DECODE_BINARY");
37444        self.write("(");
37445        self.generate_expression(&e.this)?;
37446        if let Some(alphabet) = &e.alphabet {
37447            self.write(", ");
37448            self.generate_expression(alphabet)?;
37449        }
37450        self.write(")");
37451        Ok(())
37452    }
37453
37454    fn generate_try_base64_decode_string(&mut self, e: &TryBase64DecodeString) -> Result<()> {
37455        // TRY_BASE64_DECODE_STRING(this, [alphabet])
37456        self.write_keyword("TRY_BASE64_DECODE_STRING");
37457        self.write("(");
37458        self.generate_expression(&e.this)?;
37459        if let Some(alphabet) = &e.alphabet {
37460            self.write(", ");
37461            self.generate_expression(alphabet)?;
37462        }
37463        self.write(")");
37464        Ok(())
37465    }
37466
37467    fn generate_try_to_decfloat(&mut self, e: &TryToDecfloat) -> Result<()> {
37468        // TRY_TO_DECFLOAT(this, [format])
37469        self.write_keyword("TRY_TO_DECFLOAT");
37470        self.write("(");
37471        self.generate_expression(&e.this)?;
37472        if let Some(format) = &e.format {
37473            self.write(", '");
37474            self.write(format);
37475            self.write("'");
37476        }
37477        self.write(")");
37478        Ok(())
37479    }
37480
37481    fn generate_ts_or_ds_add(&mut self, e: &TsOrDsAdd) -> Result<()> {
37482        // TS_OR_DS_ADD(this, expression, [unit], [return_type])
37483        self.write_keyword("TS_OR_DS_ADD");
37484        self.write("(");
37485        self.generate_expression(&e.this)?;
37486        self.write(", ");
37487        self.generate_expression(&e.expression)?;
37488        if let Some(unit) = &e.unit {
37489            self.write(", ");
37490            self.write_keyword(unit);
37491        }
37492        if let Some(return_type) = &e.return_type {
37493            self.write(", ");
37494            self.generate_expression(return_type)?;
37495        }
37496        self.write(")");
37497        Ok(())
37498    }
37499
37500    fn generate_ts_or_ds_diff(&mut self, e: &TsOrDsDiff) -> Result<()> {
37501        // TS_OR_DS_DIFF(this, expression, [unit])
37502        self.write_keyword("TS_OR_DS_DIFF");
37503        self.write("(");
37504        self.generate_expression(&e.this)?;
37505        self.write(", ");
37506        self.generate_expression(&e.expression)?;
37507        if let Some(unit) = &e.unit {
37508            self.write(", ");
37509            self.write_keyword(unit);
37510        }
37511        self.write(")");
37512        Ok(())
37513    }
37514
37515    fn generate_ts_or_ds_to_date(&mut self, e: &TsOrDsToDate) -> Result<()> {
37516        let default_time_format = "%Y-%m-%d %H:%M:%S";
37517        let default_date_format = "%Y-%m-%d";
37518        let has_non_default_format = e.format.as_ref().map_or(false, |f| {
37519            f != default_time_format && f != default_date_format
37520        });
37521
37522        if has_non_default_format {
37523            // With non-default format: dialect-specific handling
37524            let fmt = e.format.as_ref().unwrap();
37525            match self.config.dialect {
37526                Some(DialectType::MySQL) | Some(DialectType::StarRocks) => {
37527                    // MySQL/StarRocks: STR_TO_DATE(x, fmt) - no CAST wrapper
37528                    // STR_TO_DATE is the MySQL-native form of StrToTime
37529                    let str_to_time = crate::expressions::StrToTime {
37530                        this: Box::new((*e.this).clone()),
37531                        format: fmt.clone(),
37532                        zone: None,
37533                        safe: None,
37534                        target_type: None,
37535                    };
37536                    self.generate_str_to_time(&str_to_time)?;
37537                }
37538                Some(DialectType::Hive)
37539                | Some(DialectType::Spark)
37540                | Some(DialectType::Databricks) => {
37541                    // Hive/Spark: TO_DATE(x, java_fmt)
37542                    self.write_keyword("TO_DATE");
37543                    self.write("(");
37544                    self.generate_expression(&e.this)?;
37545                    self.write(", '");
37546                    self.write(&Self::strftime_to_java_format(fmt));
37547                    self.write("')");
37548                }
37549                Some(DialectType::Snowflake) => {
37550                    // Snowflake: TO_DATE(x, snowflake_fmt)
37551                    self.write_keyword("TO_DATE");
37552                    self.write("(");
37553                    self.generate_expression(&e.this)?;
37554                    self.write(", '");
37555                    self.write(&Self::strftime_to_snowflake_format(fmt));
37556                    self.write("')");
37557                }
37558                Some(DialectType::Doris) => {
37559                    // Doris: TO_DATE(x) - ignores format
37560                    self.write_keyword("TO_DATE");
37561                    self.write("(");
37562                    self.generate_expression(&e.this)?;
37563                    self.write(")");
37564                }
37565                _ => {
37566                    // Default: CAST(STR_TO_TIME(x, fmt) AS DATE)
37567                    self.write_keyword("CAST");
37568                    self.write("(");
37569                    let str_to_time = crate::expressions::StrToTime {
37570                        this: Box::new((*e.this).clone()),
37571                        format: fmt.clone(),
37572                        zone: None,
37573                        safe: None,
37574                        target_type: None,
37575                    };
37576                    self.generate_str_to_time(&str_to_time)?;
37577                    self.write_keyword(" AS ");
37578                    self.write_keyword("DATE");
37579                    self.write(")");
37580                }
37581            }
37582        } else {
37583            // Without format (or default format): simple date conversion
37584            match self.config.dialect {
37585                Some(DialectType::MySQL)
37586                | Some(DialectType::SQLite)
37587                | Some(DialectType::StarRocks) => {
37588                    // MySQL/SQLite/StarRocks: DATE(x)
37589                    self.write_keyword("DATE");
37590                    self.write("(");
37591                    self.generate_expression(&e.this)?;
37592                    self.write(")");
37593                }
37594                Some(DialectType::Hive)
37595                | Some(DialectType::Spark)
37596                | Some(DialectType::Databricks)
37597                | Some(DialectType::Snowflake)
37598                | Some(DialectType::Doris) => {
37599                    // Hive/Spark/Databricks/Snowflake/Doris: TO_DATE(x)
37600                    self.write_keyword("TO_DATE");
37601                    self.write("(");
37602                    self.generate_expression(&e.this)?;
37603                    self.write(")");
37604                }
37605                Some(DialectType::Presto)
37606                | Some(DialectType::Trino)
37607                | Some(DialectType::Athena) => {
37608                    // Presto/Trino: CAST(CAST(x AS TIMESTAMP) AS DATE)
37609                    self.write_keyword("CAST");
37610                    self.write("(");
37611                    self.write_keyword("CAST");
37612                    self.write("(");
37613                    self.generate_expression(&e.this)?;
37614                    self.write_keyword(" AS ");
37615                    self.write_keyword("TIMESTAMP");
37616                    self.write(")");
37617                    self.write_keyword(" AS ");
37618                    self.write_keyword("DATE");
37619                    self.write(")");
37620                }
37621                Some(DialectType::ClickHouse) => {
37622                    // ClickHouse: CAST(x AS Nullable(DATE))
37623                    self.write_keyword("CAST");
37624                    self.write("(");
37625                    self.generate_expression(&e.this)?;
37626                    self.write_keyword(" AS ");
37627                    self.write("Nullable(DATE)");
37628                    self.write(")");
37629                }
37630                _ => {
37631                    // Default: CAST(x AS DATE)
37632                    self.write_keyword("CAST");
37633                    self.write("(");
37634                    self.generate_expression(&e.this)?;
37635                    self.write_keyword(" AS ");
37636                    self.write_keyword("DATE");
37637                    self.write(")");
37638                }
37639            }
37640        }
37641        Ok(())
37642    }
37643
37644    fn generate_ts_or_ds_to_time(&mut self, e: &TsOrDsToTime) -> Result<()> {
37645        // TS_OR_DS_TO_TIME(this, [format])
37646        self.write_keyword("TS_OR_DS_TO_TIME");
37647        self.write("(");
37648        self.generate_expression(&e.this)?;
37649        if let Some(format) = &e.format {
37650            self.write(", '");
37651            self.write(format);
37652            self.write("'");
37653        }
37654        self.write(")");
37655        Ok(())
37656    }
37657
37658    fn generate_unhex(&mut self, e: &Unhex) -> Result<()> {
37659        // UNHEX(this, [expression])
37660        self.write_keyword("UNHEX");
37661        self.write("(");
37662        self.generate_expression(&e.this)?;
37663        if let Some(expression) = &e.expression {
37664            self.write(", ");
37665            self.generate_expression(expression)?;
37666        }
37667        self.write(")");
37668        Ok(())
37669    }
37670
37671    fn generate_unicode_string(&mut self, e: &UnicodeString) -> Result<()> {
37672        // U&this [UESCAPE escape]
37673        self.write("U&");
37674        self.generate_expression(&e.this)?;
37675        if let Some(escape) = &e.escape {
37676            self.write_space();
37677            self.write_keyword("UESCAPE");
37678            self.write_space();
37679            self.generate_expression(escape)?;
37680        }
37681        Ok(())
37682    }
37683
37684    fn generate_uniform(&mut self, e: &Uniform) -> Result<()> {
37685        // UNIFORM(this, expression, [gen], [seed])
37686        self.write_keyword("UNIFORM");
37687        self.write("(");
37688        self.generate_expression(&e.this)?;
37689        self.write(", ");
37690        self.generate_expression(&e.expression)?;
37691        if let Some(gen) = &e.gen {
37692            self.write(", ");
37693            self.generate_expression(gen)?;
37694        }
37695        if let Some(seed) = &e.seed {
37696            self.write(", ");
37697            self.generate_expression(seed)?;
37698        }
37699        self.write(")");
37700        Ok(())
37701    }
37702
37703    fn generate_unique_column_constraint(&mut self, e: &UniqueColumnConstraint) -> Result<()> {
37704        // UNIQUE [NULLS NOT DISTINCT] [this] [index_type] [on_conflict] [options]
37705        self.write_keyword("UNIQUE");
37706        // Output NULLS NOT DISTINCT if nulls is set (PostgreSQL 15+ feature)
37707        if e.nulls.is_some() {
37708            self.write(" NULLS NOT DISTINCT");
37709        }
37710        if let Some(this) = &e.this {
37711            self.write_space();
37712            self.generate_expression(this)?;
37713        }
37714        if let Some(index_type) = &e.index_type {
37715            self.write(" USING ");
37716            self.generate_expression(index_type)?;
37717        }
37718        if let Some(on_conflict) = &e.on_conflict {
37719            self.write_space();
37720            self.generate_expression(on_conflict)?;
37721        }
37722        for opt in &e.options {
37723            self.write_space();
37724            self.generate_expression(opt)?;
37725        }
37726        Ok(())
37727    }
37728
37729    fn generate_unique_key_property(&mut self, e: &UniqueKeyProperty) -> Result<()> {
37730        // UNIQUE KEY (expressions)
37731        self.write_keyword("UNIQUE KEY");
37732        self.write(" (");
37733        for (i, expr) in e.expressions.iter().enumerate() {
37734            if i > 0 {
37735                self.write(", ");
37736            }
37737            self.generate_expression(expr)?;
37738        }
37739        self.write(")");
37740        Ok(())
37741    }
37742
37743    fn generate_rollup_property(&mut self, e: &RollupProperty) -> Result<()> {
37744        // ROLLUP (r1(col1, col2), r2(col1))
37745        self.write_keyword("ROLLUP");
37746        self.write(" (");
37747        for (i, index) in e.expressions.iter().enumerate() {
37748            if i > 0 {
37749                self.write(", ");
37750            }
37751            self.generate_identifier(&index.name)?;
37752            self.write("(");
37753            for (j, col) in index.expressions.iter().enumerate() {
37754                if j > 0 {
37755                    self.write(", ");
37756                }
37757                self.generate_identifier(col)?;
37758            }
37759            self.write(")");
37760        }
37761        self.write(")");
37762        Ok(())
37763    }
37764
37765    fn generate_unix_to_str(&mut self, e: &UnixToStr) -> Result<()> {
37766        match self.config.dialect {
37767            Some(DialectType::DuckDB) => {
37768                // DuckDB: STRFTIME(TO_TIMESTAMP(value), format)
37769                self.write_keyword("STRFTIME");
37770                self.write("(");
37771                self.write_keyword("TO_TIMESTAMP");
37772                self.write("(");
37773                self.generate_expression(&e.this)?;
37774                self.write("), '");
37775                if let Some(format) = &e.format {
37776                    self.write(format);
37777                }
37778                self.write("')");
37779            }
37780            Some(DialectType::Hive) => {
37781                // Hive: FROM_UNIXTIME(value, format) - elide format when it's the default
37782                self.write_keyword("FROM_UNIXTIME");
37783                self.write("(");
37784                self.generate_expression(&e.this)?;
37785                if let Some(format) = &e.format {
37786                    if format != "yyyy-MM-dd HH:mm:ss" {
37787                        self.write(", '");
37788                        self.write(format);
37789                        self.write("'");
37790                    }
37791                }
37792                self.write(")");
37793            }
37794            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37795                // Presto: DATE_FORMAT(FROM_UNIXTIME(value), format)
37796                self.write_keyword("DATE_FORMAT");
37797                self.write("(");
37798                self.write_keyword("FROM_UNIXTIME");
37799                self.write("(");
37800                self.generate_expression(&e.this)?;
37801                self.write("), '");
37802                if let Some(format) = &e.format {
37803                    self.write(format);
37804                }
37805                self.write("')");
37806            }
37807            Some(DialectType::Spark) | Some(DialectType::Databricks) => {
37808                // Spark: FROM_UNIXTIME(value, format)
37809                self.write_keyword("FROM_UNIXTIME");
37810                self.write("(");
37811                self.generate_expression(&e.this)?;
37812                if let Some(format) = &e.format {
37813                    self.write(", '");
37814                    self.write(format);
37815                    self.write("'");
37816                }
37817                self.write(")");
37818            }
37819            _ => {
37820                // Default: UNIX_TO_STR(this, [format])
37821                self.write_keyword("UNIX_TO_STR");
37822                self.write("(");
37823                self.generate_expression(&e.this)?;
37824                if let Some(format) = &e.format {
37825                    self.write(", '");
37826                    self.write(format);
37827                    self.write("'");
37828                }
37829                self.write(")");
37830            }
37831        }
37832        Ok(())
37833    }
37834
37835    fn generate_unix_to_time(&mut self, e: &UnixToTime) -> Result<()> {
37836        use crate::dialects::DialectType;
37837        let scale = e.scale.unwrap_or(0); // 0 = seconds
37838
37839        match self.config.dialect {
37840            Some(DialectType::Snowflake) => {
37841                // Snowflake: TO_TIMESTAMP(value[, scale]) - skip scale for seconds (0)
37842                self.write_keyword("TO_TIMESTAMP");
37843                self.write("(");
37844                self.generate_expression(&e.this)?;
37845                if let Some(s) = e.scale {
37846                    if s > 0 {
37847                        self.write(", ");
37848                        self.write(&s.to_string());
37849                    }
37850                }
37851                self.write(")");
37852            }
37853            Some(DialectType::BigQuery) => {
37854                // BigQuery: TIMESTAMP_SECONDS(value) / TIMESTAMP_MILLIS(value)
37855                // or TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64)) for other scales
37856                match scale {
37857                    0 => {
37858                        self.write_keyword("TIMESTAMP_SECONDS");
37859                        self.write("(");
37860                        self.generate_expression(&e.this)?;
37861                        self.write(")");
37862                    }
37863                    3 => {
37864                        self.write_keyword("TIMESTAMP_MILLIS");
37865                        self.write("(");
37866                        self.generate_expression(&e.this)?;
37867                        self.write(")");
37868                    }
37869                    6 => {
37870                        self.write_keyword("TIMESTAMP_MICROS");
37871                        self.write("(");
37872                        self.generate_expression(&e.this)?;
37873                        self.write(")");
37874                    }
37875                    _ => {
37876                        // TIMESTAMP_SECONDS(CAST(value / POWER(10, scale) AS INT64))
37877                        self.write_keyword("TIMESTAMP_SECONDS");
37878                        self.write("(CAST(");
37879                        self.generate_expression(&e.this)?;
37880                        self.write(&format!(" / POWER(10, {}) AS INT64))", scale));
37881                    }
37882                }
37883            }
37884            Some(DialectType::Spark) => {
37885                // Spark: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
37886                // TIMESTAMP_MILLIS(value) for scale=3
37887                // TIMESTAMP_MICROS(value) for scale=6
37888                // TIMESTAMP_SECONDS(value / POWER(10, scale)) for other scales
37889                match scale {
37890                    0 => {
37891                        self.write_keyword("CAST");
37892                        self.write("(");
37893                        self.write_keyword("FROM_UNIXTIME");
37894                        self.write("(");
37895                        self.generate_expression(&e.this)?;
37896                        self.write(") ");
37897                        self.write_keyword("AS TIMESTAMP");
37898                        self.write(")");
37899                    }
37900                    3 => {
37901                        self.write_keyword("TIMESTAMP_MILLIS");
37902                        self.write("(");
37903                        self.generate_expression(&e.this)?;
37904                        self.write(")");
37905                    }
37906                    6 => {
37907                        self.write_keyword("TIMESTAMP_MICROS");
37908                        self.write("(");
37909                        self.generate_expression(&e.this)?;
37910                        self.write(")");
37911                    }
37912                    _ => {
37913                        self.write_keyword("TIMESTAMP_SECONDS");
37914                        self.write("(");
37915                        self.generate_expression(&e.this)?;
37916                        self.write(&format!(" / POWER(10, {}))", scale));
37917                    }
37918                }
37919            }
37920            Some(DialectType::Databricks) => {
37921                // Databricks: CAST(FROM_UNIXTIME(value) AS TIMESTAMP) for scale=0
37922                // TIMESTAMP_MILLIS(value) for scale=3
37923                // TIMESTAMP_MICROS(value) for scale=6
37924                match scale {
37925                    0 => {
37926                        self.write_keyword("CAST");
37927                        self.write("(");
37928                        self.write_keyword("FROM_UNIXTIME");
37929                        self.write("(");
37930                        self.generate_expression(&e.this)?;
37931                        self.write(") ");
37932                        self.write_keyword("AS TIMESTAMP");
37933                        self.write(")");
37934                    }
37935                    3 => {
37936                        self.write_keyword("TIMESTAMP_MILLIS");
37937                        self.write("(");
37938                        self.generate_expression(&e.this)?;
37939                        self.write(")");
37940                    }
37941                    6 => {
37942                        self.write_keyword("TIMESTAMP_MICROS");
37943                        self.write("(");
37944                        self.generate_expression(&e.this)?;
37945                        self.write(")");
37946                    }
37947                    _ => {
37948                        self.write_keyword("TIMESTAMP_SECONDS");
37949                        self.write("(");
37950                        self.generate_expression(&e.this)?;
37951                        self.write(&format!(" / POWER(10, {}))", scale));
37952                    }
37953                }
37954            }
37955            Some(DialectType::Hive) => {
37956                // Hive: FROM_UNIXTIME(value)
37957                if scale == 0 {
37958                    self.write_keyword("FROM_UNIXTIME");
37959                    self.write("(");
37960                    self.generate_expression(&e.this)?;
37961                    self.write(")");
37962                } else {
37963                    self.write_keyword("FROM_UNIXTIME");
37964                    self.write("(");
37965                    self.generate_expression(&e.this)?;
37966                    self.write(&format!(" / POWER(10, {})", scale));
37967                    self.write(")");
37968                }
37969            }
37970            Some(DialectType::Presto) | Some(DialectType::Trino) => {
37971                // Presto: FROM_UNIXTIME(CAST(value AS DOUBLE) / POW(10, scale)) for scale > 0
37972                // FROM_UNIXTIME(value) for scale=0
37973                if scale == 0 {
37974                    self.write_keyword("FROM_UNIXTIME");
37975                    self.write("(");
37976                    self.generate_expression(&e.this)?;
37977                    self.write(")");
37978                } else {
37979                    self.write_keyword("FROM_UNIXTIME");
37980                    self.write("(CAST(");
37981                    self.generate_expression(&e.this)?;
37982                    self.write(&format!(" AS DOUBLE) / POW(10, {}))", scale));
37983                }
37984            }
37985            Some(DialectType::DuckDB) => {
37986                // DuckDB: TO_TIMESTAMP(value) for scale=0
37987                // EPOCH_MS(value) for scale=3
37988                // MAKE_TIMESTAMP(value) for scale=6
37989                match scale {
37990                    0 => {
37991                        self.write_keyword("TO_TIMESTAMP");
37992                        self.write("(");
37993                        self.generate_expression(&e.this)?;
37994                        self.write(")");
37995                    }
37996                    3 => {
37997                        self.write_keyword("EPOCH_MS");
37998                        self.write("(");
37999                        self.generate_expression(&e.this)?;
38000                        self.write(")");
38001                    }
38002                    6 => {
38003                        self.write_keyword("MAKE_TIMESTAMP");
38004                        self.write("(");
38005                        self.generate_expression(&e.this)?;
38006                        self.write(")");
38007                    }
38008                    _ => {
38009                        self.write_keyword("TO_TIMESTAMP");
38010                        self.write("(");
38011                        self.generate_expression(&e.this)?;
38012                        self.write(&format!(" / POWER(10, {}))", scale));
38013                        self.write_keyword(" AT TIME ZONE");
38014                        self.write(" 'UTC'");
38015                    }
38016                }
38017            }
38018            Some(DialectType::Doris) | Some(DialectType::StarRocks) => {
38019                // Doris/StarRocks: FROM_UNIXTIME(value)
38020                self.write_keyword("FROM_UNIXTIME");
38021                self.write("(");
38022                self.generate_expression(&e.this)?;
38023                self.write(")");
38024            }
38025            Some(DialectType::Oracle) => {
38026                // Oracle: TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)
38027                self.write("TO_DATE('1970-01-01', 'YYYY-MM-DD') + (");
38028                self.generate_expression(&e.this)?;
38029                self.write(" / 86400)");
38030            }
38031            Some(DialectType::Redshift) => {
38032                // Redshift: (TIMESTAMP 'epoch' + value * INTERVAL '1 SECOND') for scale=0
38033                // (TIMESTAMP 'epoch' + (value / POWER(10, scale)) * INTERVAL '1 SECOND') for scale > 0
38034                self.write("(TIMESTAMP 'epoch' + ");
38035                if scale == 0 {
38036                    self.generate_expression(&e.this)?;
38037                } else {
38038                    self.write("(");
38039                    self.generate_expression(&e.this)?;
38040                    self.write(&format!(" / POWER(10, {}))", scale));
38041                }
38042                self.write(" * INTERVAL '1 SECOND')");
38043            }
38044            Some(DialectType::Exasol) => {
38045                // Exasol: FROM_POSIX_TIME(value)
38046                self.write_keyword("FROM_POSIX_TIME");
38047                self.write("(");
38048                self.generate_expression(&e.this)?;
38049                self.write(")");
38050            }
38051            _ => {
38052                // Default: TO_TIMESTAMP(value[, scale])
38053                self.write_keyword("TO_TIMESTAMP");
38054                self.write("(");
38055                self.generate_expression(&e.this)?;
38056                if let Some(s) = e.scale {
38057                    self.write(", ");
38058                    self.write(&s.to_string());
38059                }
38060                self.write(")");
38061            }
38062        }
38063        Ok(())
38064    }
38065
38066    fn generate_unpivot_columns(&mut self, e: &UnpivotColumns) -> Result<()> {
38067        // NAME col VALUE col1, col2, ...
38068        if !matches!(&*e.this, Expression::Null(_)) {
38069            self.write_keyword("NAME");
38070            self.write_space();
38071            self.generate_expression(&e.this)?;
38072        }
38073        if !e.expressions.is_empty() {
38074            self.write_space();
38075            self.write_keyword("VALUE");
38076            self.write_space();
38077            for (i, expr) in e.expressions.iter().enumerate() {
38078                if i > 0 {
38079                    self.write(", ");
38080                }
38081                self.generate_expression(expr)?;
38082            }
38083        }
38084        Ok(())
38085    }
38086
38087    fn generate_user_defined_function(&mut self, e: &UserDefinedFunction) -> Result<()> {
38088        // this(expressions) or (this)(expressions)
38089        if e.wrapped.is_some() {
38090            self.write("(");
38091        }
38092        self.generate_expression(&e.this)?;
38093        if e.wrapped.is_some() {
38094            self.write(")");
38095        }
38096        self.write("(");
38097        for (i, expr) in e.expressions.iter().enumerate() {
38098            if i > 0 {
38099                self.write(", ");
38100            }
38101            self.generate_expression(expr)?;
38102        }
38103        self.write(")");
38104        Ok(())
38105    }
38106
38107    fn generate_using_template_property(&mut self, e: &UsingTemplateProperty) -> Result<()> {
38108        // USING TEMPLATE this
38109        self.write_keyword("USING TEMPLATE");
38110        self.write_space();
38111        self.generate_expression(&e.this)?;
38112        Ok(())
38113    }
38114
38115    fn generate_utc_time(&mut self, _e: &UtcTime) -> Result<()> {
38116        // UTC_TIME
38117        self.write_keyword("UTC_TIME");
38118        Ok(())
38119    }
38120
38121    fn generate_utc_timestamp(&mut self, _e: &UtcTimestamp) -> Result<()> {
38122        if matches!(
38123            self.config.dialect,
38124            Some(crate::dialects::DialectType::ClickHouse)
38125        ) {
38126            self.write_keyword("CURRENT_TIMESTAMP");
38127            self.write("('UTC')");
38128        } else {
38129            self.write_keyword("UTC_TIMESTAMP");
38130        }
38131        Ok(())
38132    }
38133
38134    fn generate_uuid(&mut self, e: &Uuid) -> Result<()> {
38135        use crate::dialects::DialectType;
38136        // Choose UUID function name based on target dialect
38137        let func_name = match self.config.dialect {
38138            Some(DialectType::Snowflake) => "UUID_STRING",
38139            Some(DialectType::PostgreSQL) | Some(DialectType::Redshift) => "GEN_RANDOM_UUID",
38140            Some(DialectType::BigQuery) => "GENERATE_UUID",
38141            _ => {
38142                if let Some(name) = &e.name {
38143                    name.as_str()
38144                } else {
38145                    "UUID"
38146                }
38147            }
38148        };
38149        self.write_keyword(func_name);
38150        self.write("(");
38151        if let Some(this) = &e.this {
38152            self.generate_expression(this)?;
38153        }
38154        self.write(")");
38155        Ok(())
38156    }
38157
38158    fn generate_var_map(&mut self, e: &VarMap) -> Result<()> {
38159        // MAP(key1, value1, key2, value2, ...)
38160        self.write_keyword("MAP");
38161        self.write("(");
38162        let mut first = true;
38163        for (k, v) in e.keys.iter().zip(e.values.iter()) {
38164            if !first {
38165                self.write(", ");
38166            }
38167            self.generate_expression(k)?;
38168            self.write(", ");
38169            self.generate_expression(v)?;
38170            first = false;
38171        }
38172        self.write(")");
38173        Ok(())
38174    }
38175
38176    fn generate_vector_search(&mut self, e: &VectorSearch) -> Result<()> {
38177        // VECTOR_SEARCH(this, column_to_search, query_table, query_column_to_search, top_k, distance_type, ...)
38178        self.write_keyword("VECTOR_SEARCH");
38179        self.write("(");
38180        self.generate_expression(&e.this)?;
38181        if let Some(col) = &e.column_to_search {
38182            self.write(", ");
38183            self.generate_expression(col)?;
38184        }
38185        if let Some(query_table) = &e.query_table {
38186            self.write(", ");
38187            self.generate_expression(query_table)?;
38188        }
38189        if let Some(query_col) = &e.query_column_to_search {
38190            self.write(", ");
38191            self.generate_expression(query_col)?;
38192        }
38193        if let Some(top_k) = &e.top_k {
38194            self.write(", ");
38195            self.generate_expression(top_k)?;
38196        }
38197        if let Some(dist_type) = &e.distance_type {
38198            self.write(", ");
38199            self.generate_expression(dist_type)?;
38200        }
38201        self.write(")");
38202        Ok(())
38203    }
38204
38205    fn generate_version(&mut self, e: &Version) -> Result<()> {
38206        // Python: f"FOR {expression.name} {kind} {expr}"
38207        // e.this = Identifier("TIMESTAMP" or "VERSION")
38208        // e.kind = "AS OF" (or "BETWEEN", etc.)
38209        // e.expression = the value expression
38210        // Hive does NOT use the FOR prefix for time travel
38211        use crate::dialects::DialectType;
38212        let skip_for = matches!(
38213            self.config.dialect,
38214            Some(DialectType::Hive) | Some(DialectType::Spark) | Some(DialectType::Databricks)
38215        );
38216        if !skip_for {
38217            self.write_keyword("FOR");
38218            self.write_space();
38219        }
38220        // Extract the name from this (which is an Identifier expression)
38221        match e.this.as_ref() {
38222            Expression::Identifier(ident) => {
38223                self.write_keyword(&ident.name);
38224            }
38225            _ => {
38226                self.generate_expression(&e.this)?;
38227            }
38228        }
38229        self.write_space();
38230        self.write_keyword(&e.kind);
38231        if let Some(expression) = &e.expression {
38232            self.write_space();
38233            self.generate_expression(expression)?;
38234        }
38235        Ok(())
38236    }
38237
38238    fn generate_view_attribute_property(&mut self, e: &ViewAttributeProperty) -> Result<()> {
38239        // Python: return self.sql(expression, "this")
38240        self.generate_expression(&e.this)?;
38241        Ok(())
38242    }
38243
38244    fn generate_volatile_property(&mut self, e: &VolatileProperty) -> Result<()> {
38245        // Python: return "VOLATILE" if expression.args.get("this") is None else "NOT VOLATILE"
38246        if e.this.is_some() {
38247            self.write_keyword("NOT VOLATILE");
38248        } else {
38249            self.write_keyword("VOLATILE");
38250        }
38251        Ok(())
38252    }
38253
38254    fn generate_watermark_column_constraint(
38255        &mut self,
38256        e: &WatermarkColumnConstraint,
38257    ) -> Result<()> {
38258        // Python: f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}"
38259        self.write_keyword("WATERMARK FOR");
38260        self.write_space();
38261        self.generate_expression(&e.this)?;
38262        self.write_space();
38263        self.write_keyword("AS");
38264        self.write_space();
38265        self.generate_expression(&e.expression)?;
38266        Ok(())
38267    }
38268
38269    fn generate_week(&mut self, e: &Week) -> Result<()> {
38270        // Python: return self.func("WEEK", expression.this, expression.args.get("mode"))
38271        self.write_keyword("WEEK");
38272        self.write("(");
38273        self.generate_expression(&e.this)?;
38274        if let Some(mode) = &e.mode {
38275            self.write(", ");
38276            self.generate_expression(mode)?;
38277        }
38278        self.write(")");
38279        Ok(())
38280    }
38281
38282    fn generate_when(&mut self, e: &When) -> Result<()> {
38283        // Python: WHEN {matched}{source}{condition} THEN {then}
38284        // matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED"
38285        // source = " BY SOURCE" if MATCHED_BY_SOURCE and expression.args.get("source") else ""
38286        self.write_keyword("WHEN");
38287        self.write_space();
38288
38289        // Check if matched
38290        if let Some(matched) = &e.matched {
38291            // Check the expression - if it's a boolean true, use MATCHED, otherwise NOT MATCHED
38292            match matched.as_ref() {
38293                Expression::Boolean(b) if b.value => {
38294                    self.write_keyword("MATCHED");
38295                }
38296                _ => {
38297                    self.write_keyword("NOT MATCHED");
38298                }
38299            }
38300        } else {
38301            self.write_keyword("NOT MATCHED");
38302        }
38303
38304        // BY SOURCE / BY TARGET
38305        // source = Boolean(true) means BY SOURCE, Boolean(false) means BY TARGET
38306        // BY TARGET is the default and typically omitted in output
38307        // Only emit if the dialect supports BY SOURCE syntax
38308        if self.config.matched_by_source {
38309            if let Some(source) = &e.source {
38310                if let Expression::Boolean(b) = source.as_ref() {
38311                    if b.value {
38312                        // BY SOURCE
38313                        self.write_space();
38314                        self.write_keyword("BY SOURCE");
38315                    }
38316                    // BY TARGET (b.value == false) is omitted as it's the default
38317                } else {
38318                    // For non-boolean source, output as BY SOURCE (legacy behavior)
38319                    self.write_space();
38320                    self.write_keyword("BY SOURCE");
38321                }
38322            }
38323        }
38324
38325        // Condition
38326        if let Some(condition) = &e.condition {
38327            self.write_space();
38328            self.write_keyword("AND");
38329            self.write_space();
38330            self.generate_expression(condition)?;
38331        }
38332
38333        self.write_space();
38334        self.write_keyword("THEN");
38335        self.write_space();
38336
38337        // Generate the then expression (could be INSERT, UPDATE, DELETE)
38338        // MERGE actions are stored as Tuples with the action keyword as first element
38339        self.generate_merge_action(&e.then)?;
38340
38341        Ok(())
38342    }
38343
38344    fn generate_merge_action(&mut self, action: &Expression) -> Result<()> {
38345        match action {
38346            Expression::Tuple(tuple) => {
38347                let elements = &tuple.expressions;
38348                if elements.is_empty() {
38349                    return self.generate_expression(action);
38350                }
38351                // Check if first element is a Var (INSERT, UPDATE, DELETE, etc.)
38352                match &elements[0] {
38353                    Expression::Var(v) if v.this == "INSERT" => {
38354                        self.write_keyword("INSERT");
38355                        // Spark: INSERT * (insert all columns)
38356                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
38357                            self.write(" *");
38358                            if let Some(Expression::Where(w)) = elements.get(2) {
38359                                self.write_space();
38360                                self.generate_where(w)?;
38361                            }
38362                        } else {
38363                            let mut values_idx = 1;
38364                            // Check if second element is column list (Tuple)
38365                            if elements.len() > 1 {
38366                                if let Expression::Tuple(cols) = &elements[1] {
38367                                    // Could be columns or values - if there's a third element, second is columns
38368                                    if elements.len() > 2 {
38369                                        // Second is columns, third is values
38370                                        self.write(" (");
38371                                        for (i, col) in cols.expressions.iter().enumerate() {
38372                                            if i > 0 {
38373                                                self.write(", ");
38374                                            }
38375                                            // Strip MERGE target qualifiers from INSERT column list
38376                                            if !self.merge_strip_qualifiers.is_empty() {
38377                                                let stripped = self.strip_merge_qualifier(col);
38378                                                self.generate_expression(&stripped)?;
38379                                            } else {
38380                                                self.generate_expression(col)?;
38381                                            }
38382                                        }
38383                                        self.write(")");
38384                                        values_idx = 2;
38385                                    } else {
38386                                        // Only two elements: INSERT + values (no explicit columns)
38387                                        values_idx = 1;
38388                                    }
38389                                }
38390                            }
38391                            let mut next_idx = values_idx;
38392                            // Generate VALUES clause
38393                            if values_idx < elements.len()
38394                                && !matches!(&elements[values_idx], Expression::Where(_))
38395                            {
38396                                // Check if it's INSERT ROW (BigQuery) — no VALUES keyword needed
38397                                let is_row = matches!(&elements[values_idx], Expression::Var(v) if v.this == "ROW");
38398                                if !is_row {
38399                                    self.write_space();
38400                                    self.write_keyword("VALUES");
38401                                }
38402                                self.write(" ");
38403                                if let Expression::Tuple(vals) = &elements[values_idx] {
38404                                    self.write("(");
38405                                    for (i, val) in vals.expressions.iter().enumerate() {
38406                                        if i > 0 {
38407                                            self.write(", ");
38408                                        }
38409                                        self.generate_expression(val)?;
38410                                    }
38411                                    self.write(")");
38412                                } else {
38413                                    self.generate_expression(&elements[values_idx])?;
38414                                }
38415                                next_idx += 1;
38416                            }
38417                            if let Some(Expression::Where(w)) = elements.get(next_idx) {
38418                                self.write_space();
38419                                self.generate_where(w)?;
38420                            }
38421                        } // close else for INSERT * check
38422                    }
38423                    Expression::Var(v) if v.this == "UPDATE" => {
38424                        self.write_keyword("UPDATE");
38425                        // Spark: UPDATE * (update all columns)
38426                        if elements.len() > 1 && matches!(&elements[1], Expression::Star(_)) {
38427                            self.write(" *");
38428                            if let Some(Expression::Where(w)) = elements.get(2) {
38429                                self.write_space();
38430                                self.generate_where(w)?;
38431                            }
38432                        } else if elements.len() > 1 {
38433                            self.write_space();
38434                            self.write_keyword("SET");
38435                            // In pretty mode, put assignments on next line with extra indent
38436                            if self.config.pretty {
38437                                self.write_newline();
38438                                self.indent_level += 1;
38439                                self.write_indent();
38440                            } else {
38441                                self.write_space();
38442                            }
38443                            if let Expression::Tuple(assignments) = &elements[1] {
38444                                for (i, assignment) in assignments.expressions.iter().enumerate() {
38445                                    if i > 0 {
38446                                        if self.config.pretty {
38447                                            self.write(",");
38448                                            self.write_newline();
38449                                            self.write_indent();
38450                                        } else {
38451                                            self.write(", ");
38452                                        }
38453                                    }
38454                                    // Strip MERGE target qualifiers from left side of UPDATE SET
38455                                    if !self.merge_strip_qualifiers.is_empty() {
38456                                        self.generate_merge_set_assignment(assignment)?;
38457                                    } else {
38458                                        self.generate_expression(assignment)?;
38459                                    }
38460                                }
38461                            } else {
38462                                self.generate_expression(&elements[1])?;
38463                            }
38464                            if self.config.pretty {
38465                                self.indent_level -= 1;
38466                            }
38467                            if let Some(Expression::Where(w)) = elements.get(2) {
38468                                self.write_space();
38469                                self.generate_where(w)?;
38470                            }
38471                        }
38472                    }
38473                    Expression::Var(v) if v.this == "DELETE" => {
38474                        self.write_keyword("DELETE");
38475                        if let Some(Expression::Where(w)) = elements.get(1) {
38476                            self.write_space();
38477                            self.generate_where(w)?;
38478                        }
38479                    }
38480                    _ => {
38481                        // Fallback: generic tuple generation
38482                        self.generate_expression(action)?;
38483                    }
38484                }
38485            }
38486            Expression::Var(v)
38487                if v.this == "INSERT"
38488                    || v.this == "UPDATE"
38489                    || v.this == "DELETE"
38490                    || v.this == "DO NOTHING" =>
38491            {
38492                self.write_keyword(&v.this);
38493            }
38494            _ => {
38495                self.generate_expression(action)?;
38496            }
38497        }
38498        Ok(())
38499    }
38500
38501    /// Generate a MERGE UPDATE SET assignment, stripping target table qualifier from left side
38502    fn generate_merge_set_assignment(&mut self, assignment: &Expression) -> Result<()> {
38503        match assignment {
38504            Expression::Eq(eq) => {
38505                // Strip qualifier from the left side if it matches a MERGE target name
38506                let stripped_left = self.strip_merge_qualifier(&eq.left);
38507                self.generate_expression(&stripped_left)?;
38508                self.write(" = ");
38509                self.generate_expression(&eq.right)?;
38510                Ok(())
38511            }
38512            other => self.generate_expression(other),
38513        }
38514    }
38515
38516    /// Strip table qualifier from a column reference if it matches a MERGE target name
38517    fn strip_merge_qualifier(&self, expr: &Expression) -> Expression {
38518        match expr {
38519            Expression::Column(col) => {
38520                if let Some(ref table_ident) = col.table {
38521                    if self
38522                        .merge_strip_qualifiers
38523                        .iter()
38524                        .any(|n| n.eq_ignore_ascii_case(&table_ident.name))
38525                    {
38526                        // Strip the table qualifier
38527                        let mut col = col.clone();
38528                        col.table = None;
38529                        return Expression::Column(col);
38530                    }
38531                }
38532                expr.clone()
38533            }
38534            Expression::Dot(dot) => {
38535                // table.column -> column (strip qualifier)
38536                if let Expression::Identifier(id) = &dot.this {
38537                    if self
38538                        .merge_strip_qualifiers
38539                        .iter()
38540                        .any(|n| n.eq_ignore_ascii_case(&id.name))
38541                    {
38542                        return Expression::Identifier(dot.field.clone());
38543                    }
38544                }
38545                expr.clone()
38546            }
38547            _ => expr.clone(),
38548        }
38549    }
38550
38551    fn generate_whens(&mut self, e: &Whens) -> Result<()> {
38552        // Python: return self.expressions(expression, sep=" ", indent=False)
38553        for (i, expr) in e.expressions.iter().enumerate() {
38554            if i > 0 {
38555                // In pretty mode, each WHEN clause on its own line
38556                if self.config.pretty {
38557                    self.write_newline();
38558                    self.write_indent();
38559                } else {
38560                    self.write_space();
38561                }
38562            }
38563            self.generate_expression(expr)?;
38564        }
38565        Ok(())
38566    }
38567
38568    fn generate_where(&mut self, e: &Where) -> Result<()> {
38569        // Python: return f"{self.seg('WHERE')}{self.sep()}{this}"
38570        self.write_keyword("WHERE");
38571        self.write_space();
38572        self.generate_expression(&e.this)?;
38573        Ok(())
38574    }
38575
38576    fn generate_width_bucket(&mut self, e: &WidthBucket) -> Result<()> {
38577        // Python: return self.func("WIDTH_BUCKET", expression.this, ...)
38578        self.write_keyword("WIDTH_BUCKET");
38579        self.write("(");
38580        self.generate_expression(&e.this)?;
38581        if let Some(min_value) = &e.min_value {
38582            self.write(", ");
38583            self.generate_expression(min_value)?;
38584        }
38585        if let Some(max_value) = &e.max_value {
38586            self.write(", ");
38587            self.generate_expression(max_value)?;
38588        }
38589        if let Some(num_buckets) = &e.num_buckets {
38590            self.write(", ");
38591            self.generate_expression(num_buckets)?;
38592        }
38593        self.write(")");
38594        Ok(())
38595    }
38596
38597    fn generate_window(&mut self, e: &WindowSpec) -> Result<()> {
38598        // Window specification: PARTITION BY ... ORDER BY ... frame
38599        self.generate_window_spec(e)
38600    }
38601
38602    fn generate_window_spec(&mut self, e: &WindowSpec) -> Result<()> {
38603        // Window specification: PARTITION BY ... ORDER BY ... frame
38604        let mut has_content = false;
38605
38606        // PARTITION BY
38607        if !e.partition_by.is_empty() {
38608            self.write_keyword("PARTITION BY");
38609            self.write_space();
38610            for (i, expr) in e.partition_by.iter().enumerate() {
38611                if i > 0 {
38612                    self.write(", ");
38613                }
38614                self.generate_expression(expr)?;
38615            }
38616            has_content = true;
38617        }
38618
38619        // ORDER BY
38620        if !e.order_by.is_empty() {
38621            if has_content {
38622                self.write_space();
38623            }
38624            self.write_keyword("ORDER BY");
38625            self.write_space();
38626            for (i, ordered) in e.order_by.iter().enumerate() {
38627                if i > 0 {
38628                    self.write(", ");
38629                }
38630                self.generate_expression(&ordered.this)?;
38631                if ordered.desc {
38632                    self.write_space();
38633                    self.write_keyword("DESC");
38634                } else if ordered.explicit_asc {
38635                    self.write_space();
38636                    self.write_keyword("ASC");
38637                }
38638                if let Some(nulls_first) = ordered.nulls_first {
38639                    self.write_space();
38640                    self.write_keyword("NULLS");
38641                    self.write_space();
38642                    if nulls_first {
38643                        self.write_keyword("FIRST");
38644                    } else {
38645                        self.write_keyword("LAST");
38646                    }
38647                }
38648            }
38649            has_content = true;
38650        }
38651
38652        // Frame specification
38653        if let Some(frame) = &e.frame {
38654            if has_content {
38655                self.write_space();
38656            }
38657            self.generate_window_frame(frame)?;
38658        }
38659
38660        Ok(())
38661    }
38662
38663    fn generate_with_data_property(&mut self, e: &WithDataProperty) -> Result<()> {
38664        // Python: f"WITH {'NO ' if expression.args.get('no') else ''}DATA"
38665        self.write_keyword("WITH");
38666        self.write_space();
38667        if e.no.is_some() {
38668            self.write_keyword("NO");
38669            self.write_space();
38670        }
38671        self.write_keyword("DATA");
38672
38673        // statistics
38674        if let Some(statistics) = &e.statistics {
38675            self.write_space();
38676            self.write_keyword("AND");
38677            self.write_space();
38678            // Check if statistics is true or false
38679            match statistics.as_ref() {
38680                Expression::Boolean(b) if !b.value => {
38681                    self.write_keyword("NO");
38682                    self.write_space();
38683                }
38684                _ => {}
38685            }
38686            self.write_keyword("STATISTICS");
38687        }
38688        Ok(())
38689    }
38690
38691    fn generate_with_fill(&mut self, e: &WithFill) -> Result<()> {
38692        // Python: f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
38693        self.write_keyword("WITH FILL");
38694
38695        if let Some(from_) = &e.from_ {
38696            self.write_space();
38697            self.write_keyword("FROM");
38698            self.write_space();
38699            self.generate_expression(from_)?;
38700        }
38701
38702        if let Some(to) = &e.to {
38703            self.write_space();
38704            self.write_keyword("TO");
38705            self.write_space();
38706            self.generate_expression(to)?;
38707        }
38708
38709        if let Some(step) = &e.step {
38710            self.write_space();
38711            self.write_keyword("STEP");
38712            self.write_space();
38713            self.generate_expression(step)?;
38714        }
38715
38716        if let Some(staleness) = &e.staleness {
38717            self.write_space();
38718            self.write_keyword("STALENESS");
38719            self.write_space();
38720            self.generate_expression(staleness)?;
38721        }
38722
38723        if let Some(interpolate) = &e.interpolate {
38724            self.write_space();
38725            self.write_keyword("INTERPOLATE");
38726            self.write(" (");
38727            // INTERPOLATE items use reversed alias format: name AS expression
38728            self.generate_interpolate_item(interpolate)?;
38729            self.write(")");
38730        }
38731
38732        Ok(())
38733    }
38734
38735    /// Generate INTERPOLATE items with reversed alias format (name AS expression)
38736    fn generate_interpolate_item(&mut self, expr: &Expression) -> Result<()> {
38737        match expr {
38738            Expression::Alias(alias) => {
38739                // Output as: alias_name AS expression
38740                self.generate_identifier(&alias.alias)?;
38741                self.write_space();
38742                self.write_keyword("AS");
38743                self.write_space();
38744                self.generate_expression(&alias.this)?;
38745            }
38746            Expression::Tuple(tuple) => {
38747                for (i, item) in tuple.expressions.iter().enumerate() {
38748                    if i > 0 {
38749                        self.write(", ");
38750                    }
38751                    self.generate_interpolate_item(item)?;
38752                }
38753            }
38754            other => {
38755                self.generate_expression(other)?;
38756            }
38757        }
38758        Ok(())
38759    }
38760
38761    fn generate_with_journal_table_property(&mut self, e: &WithJournalTableProperty) -> Result<()> {
38762        // Python: return f"WITH JOURNAL TABLE={self.sql(expression, 'this')}"
38763        self.write_keyword("WITH JOURNAL TABLE");
38764        self.write("=");
38765        self.generate_expression(&e.this)?;
38766        Ok(())
38767    }
38768
38769    fn generate_with_operator(&mut self, e: &WithOperator) -> Result<()> {
38770        // Python: return f"{self.sql(expression, 'this')} WITH {self.sql(expression, 'op')}"
38771        self.generate_expression(&e.this)?;
38772        self.write_space();
38773        self.write_keyword("WITH");
38774        self.write_space();
38775        self.write_keyword(&e.op);
38776        Ok(())
38777    }
38778
38779    fn generate_with_procedure_options(&mut self, e: &WithProcedureOptions) -> Result<()> {
38780        // Python: return f"WITH {self.expressions(expression, flat=True)}"
38781        self.write_keyword("WITH");
38782        self.write_space();
38783        for (i, expr) in e.expressions.iter().enumerate() {
38784            if i > 0 {
38785                self.write(", ");
38786            }
38787            self.generate_expression(expr)?;
38788        }
38789        Ok(())
38790    }
38791
38792    fn generate_with_schema_binding_property(
38793        &mut self,
38794        e: &WithSchemaBindingProperty,
38795    ) -> Result<()> {
38796        // Python: return f"WITH {self.sql(expression, 'this')}"
38797        self.write_keyword("WITH");
38798        self.write_space();
38799        self.generate_expression(&e.this)?;
38800        Ok(())
38801    }
38802
38803    fn generate_with_system_versioning_property(
38804        &mut self,
38805        e: &WithSystemVersioningProperty,
38806    ) -> Result<()> {
38807        // Python: complex logic for SYSTEM_VERSIONING with options
38808        // SYSTEM_VERSIONING=ON(HISTORY_TABLE=..., DATA_CONSISTENCY_CHECK=..., HISTORY_RETENTION_PERIOD=...)
38809        // or SYSTEM_VERSIONING=ON/OFF
38810        // with WITH(...) wrapper if with_ is set
38811
38812        let mut parts = Vec::new();
38813
38814        if let Some(this) = &e.this {
38815            // HISTORY_TABLE=...
38816            let mut s = String::from("HISTORY_TABLE=");
38817            let mut gen = Generator::new();
38818            gen.generate_expression(this)?;
38819            s.push_str(&gen.output);
38820            parts.push(s);
38821        }
38822
38823        if let Some(data_consistency) = &e.data_consistency {
38824            let mut s = String::from("DATA_CONSISTENCY_CHECK=");
38825            let mut gen = Generator::new();
38826            gen.generate_expression(data_consistency)?;
38827            s.push_str(&gen.output);
38828            parts.push(s);
38829        }
38830
38831        if let Some(retention_period) = &e.retention_period {
38832            let mut s = String::from("HISTORY_RETENTION_PERIOD=");
38833            let mut gen = Generator::new();
38834            gen.generate_expression(retention_period)?;
38835            s.push_str(&gen.output);
38836            parts.push(s);
38837        }
38838
38839        self.write_keyword("SYSTEM_VERSIONING");
38840        self.write("=");
38841
38842        if !parts.is_empty() {
38843            self.write_keyword("ON");
38844            self.write("(");
38845            self.write(&parts.join(", "));
38846            self.write(")");
38847        } else if e.on.is_some() {
38848            self.write_keyword("ON");
38849        } else {
38850            self.write_keyword("OFF");
38851        }
38852
38853        // Wrap in WITH(...) if with_ is set
38854        if e.with_.is_some() {
38855            let inner = self.output.clone();
38856            self.output.clear();
38857            self.write("WITH(");
38858            self.write(&inner);
38859            self.write(")");
38860        }
38861
38862        Ok(())
38863    }
38864
38865    fn generate_with_table_hint(&mut self, e: &WithTableHint) -> Result<()> {
38866        // Python: f"WITH ({self.expressions(expression, flat=True)})"
38867        self.write_keyword("WITH");
38868        self.write(" (");
38869        for (i, expr) in e.expressions.iter().enumerate() {
38870            if i > 0 {
38871                self.write(", ");
38872            }
38873            self.generate_expression(expr)?;
38874        }
38875        self.write(")");
38876        Ok(())
38877    }
38878
38879    fn generate_xml_element(&mut self, e: &XMLElement) -> Result<()> {
38880        // Python: prefix = "EVALNAME" if expression.args.get("evalname") else "NAME"
38881        // return self.func("XMLELEMENT", name, *expression.expressions)
38882        self.write_keyword("XMLELEMENT");
38883        self.write("(");
38884
38885        if e.evalname.is_some() {
38886            self.write_keyword("EVALNAME");
38887        } else {
38888            self.write_keyword("NAME");
38889        }
38890        self.write_space();
38891        self.generate_expression(&e.this)?;
38892
38893        for expr in &e.expressions {
38894            self.write(", ");
38895            self.generate_expression(expr)?;
38896        }
38897        self.write(")");
38898        Ok(())
38899    }
38900
38901    fn generate_xml_get(&mut self, e: &XMLGet) -> Result<()> {
38902        // XMLGET(this, expression [, instance])
38903        self.write_keyword("XMLGET");
38904        self.write("(");
38905        self.generate_expression(&e.this)?;
38906        self.write(", ");
38907        self.generate_expression(&e.expression)?;
38908        if let Some(instance) = &e.instance {
38909            self.write(", ");
38910            self.generate_expression(instance)?;
38911        }
38912        self.write(")");
38913        Ok(())
38914    }
38915
38916    fn generate_xml_key_value_option(&mut self, e: &XMLKeyValueOption) -> Result<()> {
38917        // Python: this + optional (expr)
38918        self.generate_expression(&e.this)?;
38919        if let Some(expression) = &e.expression {
38920            self.write("(");
38921            self.generate_expression(expression)?;
38922            self.write(")");
38923        }
38924        Ok(())
38925    }
38926
38927    fn generate_xml_table(&mut self, e: &XMLTable) -> Result<()> {
38928        // Python: XMLTABLE(namespaces + this + passing + by_ref + columns)
38929        self.write_keyword("XMLTABLE");
38930        self.write("(");
38931
38932        if self.config.pretty {
38933            self.indent_level += 1;
38934            self.write_newline();
38935            self.write_indent();
38936            self.generate_expression(&e.this)?;
38937
38938            if let Some(passing) = &e.passing {
38939                self.write_newline();
38940                self.write_indent();
38941                self.write_keyword("PASSING");
38942                if let Expression::Tuple(tuple) = passing.as_ref() {
38943                    for expr in &tuple.expressions {
38944                        self.write_newline();
38945                        self.indent_level += 1;
38946                        self.write_indent();
38947                        self.generate_expression(expr)?;
38948                        self.indent_level -= 1;
38949                    }
38950                } else {
38951                    self.write_newline();
38952                    self.indent_level += 1;
38953                    self.write_indent();
38954                    self.generate_expression(passing)?;
38955                    self.indent_level -= 1;
38956                }
38957            }
38958
38959            if e.by_ref.is_some() {
38960                self.write_newline();
38961                self.write_indent();
38962                self.write_keyword("RETURNING SEQUENCE BY REF");
38963            }
38964
38965            if !e.columns.is_empty() {
38966                self.write_newline();
38967                self.write_indent();
38968                self.write_keyword("COLUMNS");
38969                for (i, col) in e.columns.iter().enumerate() {
38970                    self.write_newline();
38971                    self.indent_level += 1;
38972                    self.write_indent();
38973                    self.generate_expression(col)?;
38974                    self.indent_level -= 1;
38975                    if i < e.columns.len() - 1 {
38976                        self.write(",");
38977                    }
38978                }
38979            }
38980
38981            self.indent_level -= 1;
38982            self.write_newline();
38983            self.write_indent();
38984            self.write(")");
38985            return Ok(());
38986        }
38987
38988        // Namespaces - unwrap Tuple to generate comma-separated list without parentheses
38989        if let Some(namespaces) = &e.namespaces {
38990            self.write_keyword("XMLNAMESPACES");
38991            self.write("(");
38992            // Unwrap Tuple if present to avoid extra parentheses
38993            if let Expression::Tuple(tuple) = namespaces.as_ref() {
38994                for (i, expr) in tuple.expressions.iter().enumerate() {
38995                    if i > 0 {
38996                        self.write(", ");
38997                    }
38998                    // Python pattern: if it's an Alias, output as-is; otherwise prepend DEFAULT
38999                    // See xmlnamespace_sql in generator.py
39000                    if !matches!(expr, Expression::Alias(_)) {
39001                        self.write_keyword("DEFAULT");
39002                        self.write_space();
39003                    }
39004                    self.generate_expression(expr)?;
39005                }
39006            } else {
39007                // Single namespace - check if DEFAULT
39008                if !matches!(namespaces.as_ref(), Expression::Alias(_)) {
39009                    self.write_keyword("DEFAULT");
39010                    self.write_space();
39011                }
39012                self.generate_expression(namespaces)?;
39013            }
39014            self.write("), ");
39015        }
39016
39017        // XPath expression
39018        self.generate_expression(&e.this)?;
39019
39020        // PASSING clause - unwrap Tuple to generate comma-separated list without parentheses
39021        if let Some(passing) = &e.passing {
39022            self.write_space();
39023            self.write_keyword("PASSING");
39024            self.write_space();
39025            // Unwrap Tuple if present to avoid extra parentheses
39026            if let Expression::Tuple(tuple) = passing.as_ref() {
39027                for (i, expr) in tuple.expressions.iter().enumerate() {
39028                    if i > 0 {
39029                        self.write(", ");
39030                    }
39031                    self.generate_expression(expr)?;
39032                }
39033            } else {
39034                self.generate_expression(passing)?;
39035            }
39036        }
39037
39038        // RETURNING SEQUENCE BY REF
39039        if e.by_ref.is_some() {
39040            self.write_space();
39041            self.write_keyword("RETURNING SEQUENCE BY REF");
39042        }
39043
39044        // COLUMNS clause
39045        if !e.columns.is_empty() {
39046            self.write_space();
39047            self.write_keyword("COLUMNS");
39048            self.write_space();
39049            for (i, col) in e.columns.iter().enumerate() {
39050                if i > 0 {
39051                    self.write(", ");
39052                }
39053                self.generate_expression(col)?;
39054            }
39055        }
39056
39057        self.write(")");
39058        Ok(())
39059    }
39060
39061    fn generate_xor(&mut self, e: &Xor) -> Result<()> {
39062        // Python: return self.connector_sql(expression, "XOR", stack)
39063        // Handles: this XOR expression or expressions joined by XOR
39064        if let Some(this) = &e.this {
39065            self.generate_expression(this)?;
39066            if let Some(expression) = &e.expression {
39067                self.write_space();
39068                self.write_keyword("XOR");
39069                self.write_space();
39070                self.generate_expression(expression)?;
39071            }
39072        }
39073
39074        // Handle multiple expressions
39075        for (i, expr) in e.expressions.iter().enumerate() {
39076            if i > 0 || e.this.is_some() {
39077                self.write_space();
39078                self.write_keyword("XOR");
39079                self.write_space();
39080            }
39081            self.generate_expression(expr)?;
39082        }
39083        Ok(())
39084    }
39085
39086    fn generate_zipf(&mut self, e: &Zipf) -> Result<()> {
39087        // ZIPF(this, elementcount [, gen])
39088        self.write_keyword("ZIPF");
39089        self.write("(");
39090        self.generate_expression(&e.this)?;
39091        if let Some(elementcount) = &e.elementcount {
39092            self.write(", ");
39093            self.generate_expression(elementcount)?;
39094        }
39095        if let Some(gen) = &e.gen {
39096            self.write(", ");
39097            self.generate_expression(gen)?;
39098        }
39099        self.write(")");
39100        Ok(())
39101    }
39102}
39103
39104impl Default for Generator {
39105    fn default() -> Self {
39106        Self::new()
39107    }
39108}
39109
39110#[cfg(test)]
39111mod tests {
39112    use super::*;
39113    use crate::parser::Parser;
39114
39115    fn roundtrip(sql: &str) -> String {
39116        let ast = Parser::parse_sql(sql).unwrap();
39117        Generator::sql(&ast[0]).unwrap()
39118    }
39119
39120    #[test]
39121    fn test_simple_select() {
39122        let result = roundtrip("SELECT 1");
39123        assert_eq!(result, "SELECT 1");
39124    }
39125
39126    #[test]
39127    fn test_select_from() {
39128        let result = roundtrip("SELECT a, b FROM t");
39129        assert_eq!(result, "SELECT a, b FROM t");
39130    }
39131
39132    #[test]
39133    fn test_select_where() {
39134        let result = roundtrip("SELECT * FROM t WHERE x = 1");
39135        assert_eq!(result, "SELECT * FROM t WHERE x = 1");
39136    }
39137
39138    #[test]
39139    fn test_select_join() {
39140        let result = roundtrip("SELECT * FROM a JOIN b ON a.id = b.id");
39141        assert_eq!(result, "SELECT * FROM a JOIN b ON a.id = b.id");
39142    }
39143
39144    #[test]
39145    fn test_insert() {
39146        let result = roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
39147        assert_eq!(result, "INSERT INTO t (a, b) VALUES (1, 2)");
39148    }
39149
39150    #[test]
39151    fn test_pretty_print() {
39152        let ast = Parser::parse_sql("SELECT a, b FROM t WHERE x = 1").unwrap();
39153        let result = Generator::pretty_sql(&ast[0]).unwrap();
39154        assert!(result.contains('\n'));
39155    }
39156
39157    #[test]
39158    fn test_window_function() {
39159        let result = roundtrip("SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)");
39160        assert_eq!(
39161            result,
39162            "SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id)"
39163        );
39164    }
39165
39166    #[test]
39167    fn test_window_function_with_frame() {
39168        let result = roundtrip("SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
39169        assert_eq!(result, "SELECT SUM(amount) OVER (ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)");
39170    }
39171
39172    #[test]
39173    fn test_aggregate_with_filter() {
39174        let result = roundtrip("SELECT COUNT(*) FILTER (WHERE status = 1) FROM orders");
39175        assert_eq!(
39176            result,
39177            "SELECT COUNT(*) FILTER(WHERE status = 1) FROM orders"
39178        );
39179    }
39180
39181    #[test]
39182    fn test_subscript() {
39183        let result = roundtrip("SELECT arr[0]");
39184        assert_eq!(result, "SELECT arr[0]");
39185    }
39186
39187    // DDL tests
39188    #[test]
39189    fn test_create_table() {
39190        let result = roundtrip("CREATE TABLE users (id INT, name VARCHAR(100))");
39191        assert_eq!(result, "CREATE TABLE users (id INT, name VARCHAR(100))");
39192    }
39193
39194    #[test]
39195    fn test_create_table_with_constraints() {
39196        let result = roundtrip(
39197            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)",
39198        );
39199        assert_eq!(
39200            result,
39201            "CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL)"
39202        );
39203    }
39204
39205    #[test]
39206    fn test_create_table_if_not_exists() {
39207        let result = roundtrip("CREATE TABLE IF NOT EXISTS t (id INT)");
39208        assert_eq!(result, "CREATE TABLE IF NOT EXISTS t (id INT)");
39209    }
39210
39211    #[test]
39212    fn test_drop_table() {
39213        let result = roundtrip("DROP TABLE users");
39214        assert_eq!(result, "DROP TABLE users");
39215    }
39216
39217    #[test]
39218    fn test_drop_table_if_exists_cascade() {
39219        let result = roundtrip("DROP TABLE IF EXISTS users CASCADE");
39220        assert_eq!(result, "DROP TABLE IF EXISTS users CASCADE");
39221    }
39222
39223    #[test]
39224    fn test_alter_table_add_column() {
39225        let result = roundtrip("ALTER TABLE users ADD COLUMN email VARCHAR(255)");
39226        assert_eq!(result, "ALTER TABLE users ADD COLUMN email VARCHAR(255)");
39227    }
39228
39229    #[test]
39230    fn test_alter_table_drop_column() {
39231        let result = roundtrip("ALTER TABLE users DROP COLUMN email");
39232        assert_eq!(result, "ALTER TABLE users DROP COLUMN email");
39233    }
39234
39235    #[test]
39236    fn test_create_index() {
39237        let result = roundtrip("CREATE INDEX idx_name ON users(name)");
39238        assert_eq!(result, "CREATE INDEX idx_name ON users(name)");
39239    }
39240
39241    #[test]
39242    fn test_create_unique_index() {
39243        let result = roundtrip("CREATE UNIQUE INDEX idx_email ON users(email)");
39244        assert_eq!(result, "CREATE UNIQUE INDEX idx_email ON users(email)");
39245    }
39246
39247    #[test]
39248    fn test_drop_index() {
39249        let result = roundtrip("DROP INDEX idx_name");
39250        assert_eq!(result, "DROP INDEX idx_name");
39251
39252        let result = roundtrip(r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#);
39253        assert_eq!(
39254            result,
39255            r#"DROP INDEX IF EXISTS "idx_tokenKey__pb_users_auth_""#
39256        );
39257
39258        let result = roundtrip(r#"DROP INDEX "public"."IdxMixed""#);
39259        assert_eq!(result, r#"DROP INDEX "public"."IdxMixed""#);
39260    }
39261
39262    #[test]
39263    fn test_create_view() {
39264        let result = roundtrip("CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1");
39265        assert_eq!(
39266            result,
39267            "CREATE VIEW active_users AS SELECT * FROM users WHERE active = 1"
39268        );
39269    }
39270
39271    #[test]
39272    fn test_drop_view() {
39273        let result = roundtrip("DROP VIEW active_users");
39274        assert_eq!(result, "DROP VIEW active_users");
39275    }
39276
39277    #[test]
39278    fn test_truncate() {
39279        let result = roundtrip("TRUNCATE TABLE users");
39280        assert_eq!(result, "TRUNCATE TABLE users");
39281    }
39282
39283    #[test]
39284    fn test_string_literal_escaping_default() {
39285        // Default: double single quotes
39286        let result = roundtrip("SELECT 'hello'");
39287        assert_eq!(result, "SELECT 'hello'");
39288
39289        // Single quotes are doubled
39290        let result = roundtrip("SELECT 'it''s a test'");
39291        assert_eq!(result, "SELECT 'it''s a test'");
39292    }
39293
39294    #[test]
39295    fn test_not_in_style_prefix_default_generic() {
39296        let result = roundtrip("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')");
39297        assert_eq!(
39298            result,
39299            "SELECT id FROM users WHERE NOT status IN ('deleted', 'banned')"
39300        );
39301    }
39302
39303    #[test]
39304    fn test_not_in_style_infix_generic_override() {
39305        let ast =
39306            Parser::parse_sql("SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')")
39307                .unwrap();
39308        let config = GeneratorConfig {
39309            not_in_style: NotInStyle::Infix,
39310            ..Default::default()
39311        };
39312        let mut gen = Generator::with_config(config);
39313        let result = gen.generate(&ast[0]).unwrap();
39314        assert_eq!(
39315            result,
39316            "SELECT id FROM users WHERE status NOT IN ('deleted', 'banned')"
39317        );
39318    }
39319
39320    #[test]
39321    fn test_string_literal_escaping_mysql() {
39322        use crate::dialects::DialectType;
39323
39324        let config = GeneratorConfig {
39325            dialect: Some(DialectType::MySQL),
39326            ..Default::default()
39327        };
39328
39329        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39330        let mut gen = Generator::with_config(config.clone());
39331        let result = gen.generate(&ast[0]).unwrap();
39332        assert_eq!(result, "SELECT 'hello'");
39333
39334        // MySQL uses SQL standard quote doubling for escaping (matches Python sqlglot)
39335        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39336        let mut gen = Generator::with_config(config.clone());
39337        let result = gen.generate(&ast[0]).unwrap();
39338        assert_eq!(result, "SELECT 'it''s'");
39339    }
39340
39341    #[test]
39342    fn test_string_literal_escaping_postgres() {
39343        use crate::dialects::DialectType;
39344
39345        let config = GeneratorConfig {
39346            dialect: Some(DialectType::PostgreSQL),
39347            ..Default::default()
39348        };
39349
39350        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39351        let mut gen = Generator::with_config(config.clone());
39352        let result = gen.generate(&ast[0]).unwrap();
39353        assert_eq!(result, "SELECT 'hello'");
39354
39355        // PostgreSQL uses doubled quotes for regular strings
39356        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39357        let mut gen = Generator::with_config(config.clone());
39358        let result = gen.generate(&ast[0]).unwrap();
39359        assert_eq!(result, "SELECT 'it''s'");
39360    }
39361
39362    #[test]
39363    fn test_string_literal_escaping_bigquery() {
39364        use crate::dialects::DialectType;
39365
39366        let config = GeneratorConfig {
39367            dialect: Some(DialectType::BigQuery),
39368            ..Default::default()
39369        };
39370
39371        let ast = Parser::parse_sql("SELECT 'hello'").unwrap();
39372        let mut gen = Generator::with_config(config.clone());
39373        let result = gen.generate(&ast[0]).unwrap();
39374        assert_eq!(result, "SELECT 'hello'");
39375
39376        // BigQuery escapes single quotes with backslash
39377        let ast = Parser::parse_sql("SELECT 'it''s'").unwrap();
39378        let mut gen = Generator::with_config(config.clone());
39379        let result = gen.generate(&ast[0]).unwrap();
39380        assert_eq!(result, "SELECT 'it\\'s'");
39381    }
39382
39383    #[test]
39384    fn test_generate_deep_and_chain_without_stack_growth() {
39385        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
39386            Expression::column("c0"),
39387            Expression::number(0),
39388        )));
39389
39390        for i in 1..2500 {
39391            let predicate = Expression::Eq(Box::new(BinaryOp::new(
39392                Expression::column(format!("c{i}")),
39393                Expression::number(i as i64),
39394            )));
39395            expr = Expression::And(Box::new(BinaryOp::new(expr, predicate)));
39396        }
39397
39398        let sql = Generator::sql(&expr).expect("deep AND chain should generate");
39399        assert!(sql.contains("c2499 = 2499"), "{}", sql);
39400    }
39401
39402    #[test]
39403    fn test_generate_deep_or_chain_without_stack_growth() {
39404        let mut expr = Expression::Eq(Box::new(BinaryOp::new(
39405            Expression::column("c0"),
39406            Expression::number(0),
39407        )));
39408
39409        for i in 1..2500 {
39410            let predicate = Expression::Eq(Box::new(BinaryOp::new(
39411                Expression::column(format!("c{i}")),
39412                Expression::number(i as i64),
39413            )));
39414            expr = Expression::Or(Box::new(BinaryOp::new(expr, predicate)));
39415        }
39416
39417        let sql = Generator::sql(&expr).expect("deep OR chain should generate");
39418        assert!(sql.contains("c2499 = 2499"), "{}", sql);
39419    }
39420}